Объявление функции C для возврата массива - PullRequest
46 голосов
/ 21 сентября 2009

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

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);
}

Ответы [ 5 ]

79 голосов
/ 21 сентября 2009

Несколько вещей, на которые стоит обратить внимание.

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

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

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

Во-вторых, функции в 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 HEIGHT в приведенном выше фрагменте должен быть интегральным выражением постоянной времени компиляции (или макрос, числовой литерал или арифметическое выражение, состоящее из макросов и / или числовых литералов). Я не уверен, верно ли это и для C99.

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

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

Для двумерных массивов это немного страшнее:

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 (&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(&A[0][0], WIDTH, HEIGHT);
  ...
}

  1. Даже если выражения &A[0][0] и A дают одно и то же значение (базовый адрес A), типы этих двух выражений различны. Первое выражение оценивается как простой указатель на char (char *), а второе - как указатель на двумерный массив char (char (*)[HEIGHT]).
15 голосов
/ 21 сентября 2009

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

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

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);
}

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

11 голосов
/ 21 сентября 2009

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

Если вы используете массив фиксированного размера, вы можете смоделировать его как 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() & 255;
  return b;
}

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

void board_init(Board *b);

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

1 голос
/ 21 сентября 2009

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

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

0 голосов
/ 10 марта 2015

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...