Объявление функции C для возврата массива

#arrays #c #function

#c

Вопрос:

Как я могу создать функцию, которая возвращает массив? Я попробовал это

 const int WIDTH=11;
const int HEIGHT=11;

int main() {
  char A[WIDTH][HEIGHT];
  A=rand_grid(WIDTH,HEIGHT);
  return 0;
}

// Initializes a random board.
char[][] rand_grid(int i, int k) {
  char* A[i][k];
  for(j=0;j<i;  j) {
    for(l=0;l<k;  l) {
      A[j][l]=ran(10);
    }
  }
  return A;
}

// Returns a random number from the set {0,...,9}.
int ran(int i) {
  srand((unsigned int) time(0));
  return(rand()%10);
}
 

Комментарии:

1. Не вызывайте srand() более одного раза в своей программе: это излишне замедлит работу вашей программы и, что наиболее важно, снизит случайность функции rand().

Ответ №1:

Следует отметить несколько моментов.

Прежде всего, вы не можете назначить объект массива, как вы делаете здесь:

 char A[WIDTH][HEIGHT];  
A=rand_grid(WIDTH,HEIGHT);
 

Объекты типа array не могут быть изменены.

Во-вторых, функции в C не могут возвращать типы массивов. Они могут возвращать указатели на массивы, хотя:

 char (*foo(int width))[HEIGHT]
{
  /**
   * dynamically allocate memory for a widthxHEIGHT array of char
   */
  char (*newArr)[HEIGHT] = malloc(sizeof *newArr * width);
  /**
   * initialize array contents here
   */
  return newArr;
}
 

Синтаксис немного сбивает с толку; он читается как

        foo                                   -- foo
       foo(int width)                        -- is a function
                                             -- taking an int parameter
      *foo(int width)                        -- returning a pointer
     (*foo(int width))[HEIGHT]               -- to a HEIGHT-element array
char (*foo(int width))[HEIGHT]               -- of char
 

Для C89 ВЫСОТА в приведенном выше фрагменте должна быть постоянным целочисленным выражением времени компиляции (либо макросом, числовым литералом, либо арифметическим выражением, состоящим из макросов и / или числовых литералов). Я не уверен, верно ли это и для C99.

Основываясь на опубликованном вами фрагменте, вам нужно взять уже выделенный массив и инициализировать его содержимое. Помните, что в большинстве контекстов выражение типа массива будет неявно преобразовано в указатель на базовый тип. Итак, если вы передаете N-элементный массив T функции, то фактически функция получает указатель на T:

 void foo (T *p) {...}
...
T arr[N];
foo(arr);
 

Для 2-d массивов это немного уродливее:

 void foo (T (*p)[M]) {...}
...
T arr[N][M];
foo(arr);
 

Это также зависит от того, что M известно во время компиляции, что ограничивает полезность функции. Вам нужна функция, которая может обрабатывать двумерный массив произвольного размера. Лучший известный мне способ добиться этого — вместо передачи указателя на массив передать адрес первого элемента в массиве [1] и передать количество строк и столбцов в качестве отдельных параметров:

 void foo(T *base, size_t rows, size_t cols) {...}
...
T arr[N][M];
foo (amp;arr[0][0], N, M);
 

Итак, ваша функция rand_grid будет выглядеть примерно так:

 void rand_grid(char *base, size_t rows, size_t cols)
{
  size_t i, j;
  for (i = 0; i < rows; i  )
  {
    for (j = 0; j < cols; j  )
    {
      /**
       * Since base is a simple char *, we must index it
       * as though it points to a 1-d array.  This works if
       * base points to the first element of a 2-d array,
       * since multi-dimensional arrays are contiguous.  
       */
      base[i*cols j] = initial_value();
    }
  }
}

int main(void)
{
  char A[WIDTH][HEIGHT];
  rand_grid(amp;A[0][0], WIDTH, HEIGHT);
  ...
}
 

  1. Несмотря на то, что выражения amp;A[0][0] и A выдают одно и то же значение (базовый адрес A), типы двух выражений различны. Первое выражение вычисляется как простой указатель на char ( char * ), в то время как второе вычисляется как указатель на 2-d массив char ( char (*)[HEIGHT] ) .

Комментарии:

1. Этот ответ удивительно познавателен. Спасибо!

2. Должна ли функция C , возвращающая массив, использовать аналогичный подход? (немного выходит за рамки вопроса, который я знаю, но недостаточно отличается, чтобы задать новый)

Ответ №2:

Вы не можете. Вы можете либо передать указатель на массив в качестве параметра и заставить функцию изменить его, либо сама функция может выделять данные и возвращать указатель.

в вашем случае

 void rand_grid(char A[WIDTH][HEIGHT]) {
    A[0][0] = 'A'; // or whatever you intend to do
}

main() {
    char A[WIDTH][HEIGHT];
    rand_grid(A);
}
 

Редактировать: как указал caf, на самом деле можно вернуть struct с массивом в нем, но, конечно, ни один программист на c в здравом уме этого не сделает.

Комментарии:

1. На самом деле вы можете вернуть массив по значению, заключив его в a struct , но это очень плохая идея.

2. Спасибо. Мне было интересно, как решить следующую проблему: мне нужна сетка, заполненная случайными целыми числами. Я хочу, чтобы инициализация массива не выполнялась в основной функции, поскольку это было бы полезно в остальной части программы. Как я могу использовать указатели для решения проблемы?

3. Jaska, ты должен сделать этот подвопрос отдельным, то есть новым вопросом о stackoverflow.

4. При условии, что размеры массива заданы во время компиляции, вы можете использовать фрагмент кода из моего ответа. Фактически это передача указателя.

5. Почему возвращать массив внутри структуры — плохая идея? Упаковка небольших массивов в структуры не является чем-то необычным. Вероятно, это быстрее и, безусловно, проще, чем выделение памяти и возврат указателя на нее.

Ответ №3:

Вы никогда не сможете вернуть выделенную стеком (« auto «) переменную чего-либо другого, кроме примитивного (value) типа, и struct s такого. Для других типов вам необходимо выделить память из кучи, используя malloc() или обернуть массив (фиксированного размера) в a struct .

Если вы используете массив фиксированного размера, вы можете смоделировать его как a struct и использовать struct-return:

 #define WIDTH  11
#define HEIGHT 11

typedef struct {
  unsigned char cell[WIDTH * HEIGHT];
} Board;

Board board_new(void)
{
  Board b;
  size_t i;

  for(i = 0; i < sizeof b.cell / sizeof *b.cell; i  )
    b.cell[i] = rand() amp; 255;
  return b;
}
 

Это нормально и не должно быть более дорогостоящим, чем альтернатива использования явного указателя:

 void board_init(Board *b);
 

Поскольку первый случай struct-return может быть переписан (компилятором) во второй. Это называется оптимизацией возвращаемого значения.

Комментарии:

1. Это просто неверно — вы можете возвращать struct типы, которые явно не являются примитивными. Однако это не очень хорошая идея.

2. Но типы структур будут скопированы из стека в (скрытую) локальную переменную рядом с вызывающим объектом. Это могло бы сработать, если бы массив был определен как фиксированный размер, чтобы компилятор знал размер возвращаемой структуры / массива. Но это не так. Это динамический массив…

3. malloc() — не единственный способ выделения памяти. Внутри функции может быть статическая переменная

4. Алекс, нет требования к динамическому размеру массива.

5. размотайте, я голосую против вас за использование слова «best» там, где это более чем неуместно 😉 «лучшим, вероятно, было бы смоделировать его как структуру». Я верну свой понижающий голос, если вы измените свою формулировку;-)

Ответ №4:

Если вы действительно хотите это сделать, вы можете попробовать сделать массив статическим, таким образом, хранилище для A не определяется областью действия функции, и вы действительно можете вернуть массив (в виде указателя, конечно).

Но это не очень хороший способ добиться того, чего вы пытаетесь достичь, вместо этого передайте массив функции rand_grid . Для этого и предназначена передача по адресу.

Комментарии:

1. В C. нет такого понятия, как «передача по адресу». Я уверен, что вы имеете в виду That's what passing the (value of the) array address is meant for.

Ответ №5:

Все известные мне методы возврата массива из функции имеют слабые и сильные стороны.

Обертывание в struct позволяет избежать накладных расходов на выделение и освобождение памяти, а также избежать запоминания освобождения. Эти проблемы возникают при любом решении, использующем malloc, calloc и realloc. С другой стороны, обертывание в структуре требует знания максимально возможного размера массива и явно расходует память и время выполнения для больших массивов (например, загрузка файла в память и передача содержимого файла из функции в функцию путем копирования).