Возвращение двумерного массива в C?

Недавно я начал программировать C только для удовольствия. Я очень опытный программист в C# .NET и Java в области рабочего стола, но для меня это становится слишком сложной задачей.

Я пытаюсь сделать что-то как "простое", возвращая двумерный массив из функции. Я пробовал исследовать в Интернете для этого, но мне было трудно найти что-то, что сработало.

Вот что я до сих пор. Он не совсем возвращает массив, он просто заполняет его. Но даже это не скомпилируется (я уверен, что причины должны быть очевидны для вас, если вы опытный программист C).

void new_array (int x[n][n]) {
  int i,o;

  for (i=0; i<n; i++) {
      for (o=0; o<n; o++) {
        x[i][o]=(rand() % n)-n/2;
      }
  }

  return x;
}

И использование:

int x[n][n];
new_array(x);

Что я делаю неправильно? Следует отметить, что n является константой, которая имеет значение 3.

Изменить. Здесь возникает ошибка компилятора при попытке определить константу: http://i.imgur.com/sa4JkXs.png

Ответ 1

C не обрабатывает массивы, как большинство языков; вам нужно будет понять следующие понятия, если вы хотите работать с массивами в C.

За исключением случаев, когда он является операндом оператора sizeof или унарного & или является строковым литералом, используемым для инициализации другого массива в объявлении, выражение типа "N-element array of T" будет преобразован ( "распад" ) в выражение типа "указатель на T", а значение выражения будет адресом первого элемента массива. Этот результат не является значением lvalue; он не может быть целью назначения и не может быть операндом для операторов ++ или --.

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

Верьте или нет, есть твердая техническая причина для этого; когда он изначально разрабатывал C, Деннис Ритчи заимствовал много понятий с языка программирования B. Б был "беспричинным" языком; все было сохранено как неподписанное слово или "ячейка". Память рассматривалась как линейный массив "ячеек". Когда вы объявили массив как

auto arr[N];

B выделит N "ячеек" для содержимого массива вместе с дополнительной ячейкой, привязанной к arr, чтобы сохранить смещение в первом элементе (в основном, указатель, но без семантики). Доступ к массивам был определен как *(arr+i); вы смещаете ячейки i с адреса, хранящегося в a, и разыменовали результат. Это отлично поработало для C, пока Ritchie не начал добавлять типы структур к языку. Он хотел, чтобы содержимое структуры не только описывало данные в абстрактных терминах, но и физически представляло биты. Примером, который он использовал, было что-то вроде

struct {
  int node;
  char name[14];
};

Он хотел выделить 2 байта для node, за которым сразу следует 14 байтов для элемента name. И он хотел, чтобы массив таких структур был выложен таким образом, что у вас было 2 байта, за которыми следуют 14 байт, за которыми следуют 2 байта, за которыми следуют 14 байтов и т.д. Он не мог найти хороший способ справиться с указателем массива, поэтому он полностью избавился от него. Вместо того, чтобы выделять хранилище для указателя, C просто вычисляет его из самого выражения массива. Вот почему вы не можете назначить что-либо для выражения массива; там ничего не назначить значение.

Итак, как вы возвращаете 2D-массив из функции?

Нет. Вы можете вернуть указатель на 2D-массив, например:

T (*func1(int rows))[N]
{
  T (*ap)[N] = malloc( sizeof *ap * rows );
  return ap;
}

Недостатком этого подхода является то, что N должно быть известно во время компиляции.

Если вы используете компилятор C99 или компилятор C2011, который поддерживает массивы переменной длины, вы можете сделать что-то вроде следующего:

void func2( size_t rows, size_t cols, int (**app)[cols] ) 
{
  *app = malloc( sizeof **app * rows );
  (*app)[i][j] = ...;                   // the parens are necessary
  ...
 }

Если у вас нет доступных массивов переменной длины, то по крайней мере размер столбца должен быть константой времени компиляции:

#define COLS ...
...
void func3( size_t rows, int (**app)[COLS] )
{ 
  *app = malloc( sizeof **app * rows );
  (*app)[i][j] = ...;
}

Вы можете выделить память по частям во что-то, что действует как 2D-массив, но строки не обязательно будут смежными:

int **func4( size_t rows, size_t cols )
{
  int **p = malloc( sizeof *p * rows );
  if ( p )
  {
    for ( size_t i = 0; i < rows; i++ )
    {
      p[i] = malloc( sizeof *p[i] * cols );
    }
  }
  return p;
}

p не является массивом; он указывает на ряд указателей на int. Для всех практических целей вы можете использовать это, как если бы это был 2D-массив:

 int **arr = foo( rows, cols );
 ...
 arr[i][j] = ...;
 printf( "value = %d\n", arr[k][l] );

Обратите внимание, что C не имеет мусорной коллекции; вы отвечаете за очистку своих беспорядков. В первых трех случаях это просто:

int (*arr1)[N] = func(rows);
// use arr[i][j];
...
free( arr1 );

int (*arr2)[cols];
func2( rows, cols, &arr2 );
...
free( arr2 );

int (*arr3)[N];
func3( rows, &arr3 );
...
free( arr3 );

В последнем случае, поскольку вы сделали двухэтапное выделение, вам нужно выполнить двухэтапное освобождение:

int **arr4 = func4( rows, cols );
...
for (i = 0; i < rows; i++ )
  free( arr4[i] )
free( arr4)

Ответ 2

Функция возвращает void, поэтому строка return x; является излишней. Кроме того, ваш код выглядит отлично. То есть, если у вас есть #define n 3 где-то, а не что-то вроде const int n = 3;.

Ответ 3

Вы не можете вернуть массив в C, многомерный или иначе.

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

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

Ответ 4

Чтобы вернуть (указатель на) вновь созданный массив измерений, известных во время компиляции, вы можете сделать это:

#define n 10 // Or other size.

int (*new_array(void))[n]
{
    int (*x)[n] = malloc(n * sizeof *x);
    if (!result)
        HandleErrorHere;

    for (int i = 0; i < n; ++i)
        for (int o = 0; i < n; ++o)
            x[i][o] = InitialValues;

    return x;
}

…
// In the calling function:
int (*x)[n] = new_array();

…
// When done with the array:
free(x);

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

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

Ответ 5

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

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

Что касается ошибки, единственным предупреждением, которое я получаю в GCC для этого, является warning: 'return' with a value, in function returning void, что просто означает, что вы не должны возвращать что-либо из функции void.

void new_array (int x[n][n]); то, что вы действительно делаете здесь, - это сделать указатель на массив из n целых чисел; затухающий тип int (*x)[n]. Это происходит потому, что массивы распадаются на указатели. Если вы знаете n во время компиляции, возможно, лучший способ пройти:

#define n 3
void new_array (int (*x)[n][n]) {
  int i,o;

  for (i=0; i<n; i++) {
    for (o=0; o<n; o++) {
      x[i][o]=(rand() % n)-n/2;
    }
  }
}

И назовите его

int arr[n][n];
new_array(&arr);

Ответ 6

Вы можете передавать произвольные массивы измерений, как и любую другую переменную, если вы завершаете их в структуре:

#include <stdio.h>

#define n 3

struct S {
  int a[n][n];
};


static struct S make_s(void)
{
  struct S s;

  int i, j;
  for (i = 0; i < n; i++) {
    for (j = 0; j < n; j++)
      s.a[i][j] = i + j;
  }

  return s;
}

static void print_s(struct S s)
{
  int i, j;
  for (i = 0; i < n; i++) {
    for (j = 0; j < n; j++)
      printf(" %d", s.a[i][j]);
    printf("\n");
  }
}

int main(void) {
  struct S s;

  s = make_s();
  print_s(s);

  return 0;
}

Ответ 7

Вероятно, вы объявляете n как постоянное целое число:

const int n = 3;

Вместо этого вы должны определить n как определение препроцессора:

#define n 3