2013-06-19 2 views
3

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

// Function to print the two-dimensional array 
void print(int x, int y, int a[x][y]){ 
    printf("\n"); 
    int i, j; 
    for(i = 0; i < x; i++){ 
     for(j = 0; j < y; j++) 
      printf("%d  ", a[i][j]); 
     printf("\n"); 
    } 
} 

// Function to initialize the two-dimensional array 
void init_2d(int *a, int x, int y){ 
    int i, j; 
    for(i = 0; i < x; i++){ 
     for(j = 0; j < y; j++){ 
      a[i*y + j] = i + j; 
     } 
     printf("\n"); 
    } 
} 

int main(){ 
    int m = 2, n = 3; 
    int a[m][n]; // a two dimensional whose size has been defined using m and n 
    init_2d(a, m, n); 
    print(m, n, a); 
} 

Как ни странно, все работает нормально. И это моя проблема, поскольку я не могу переварить ее логику.

Основными проблемами являются:

  1. То, что я читал в книгах, что размер двумерных массивов должны быть определены с использованием констант или символьных констант. В моем основном я определяю 2-D массив, используя переменные m и n, и все же он отлично работает. Зачем?
  2. Мне также сказали передать двумерный массив, разложив его в одномерный массив (определяя его как указатель на функцию int), то есть способ, которым я это сделал в функции init_2d. Но в функции print я делаю это с использованием двухмерного массива, размер которого был определен с использованием переменных x и y. Это нормально?
  3. Можно ли пропустить двумерный массив с помощью указателя на указатель?

Может ли кто-нибудь предложить мне хорошее чтение по этой теме, которое может очистить все мои понятия?

Я использую CodeBlocks скомпилировать мой код и компилятор является GNU GCC компилятор.

+0

2 странно - я удивлен, что он скомпилирован! – John3136

+0

@ John3136 C99 поддерживает форвардные декларации параметров. –

+0

@Armin: Что такое «декларация параметров форварда» и как это соотносится с комментарием John3136? – AnT

ответ

7
  1. Да и нет. То, что вы читаете в книгах, верно для более старой, оригинальной версии спецификации языка C - C89/90. Поскольку C99-версия компиляторов языка C поддерживает так называемые массивы переменной длины (VLAs), размер которых может быть задан значениями времени выполнения.Вы случайно использовали эту особенность языка C99, которая, по-видимому, поддерживается вашим компилятором в режиме по умолчанию. Если вы попросите, чтобы ваш компилятор переключился на строгий режим C89/90, ваш код не сможет скомпилироваться специально, потому что вы использовали не константы для указания размеров массива.

    Обратите внимание, что даже в C99 VLAs поддерживаются только в определенных контекстах, таких как локальные массивы и объявления параметров функций. Вы не сможете объявить статическую VLA или VLA, которая является членом типа struct. В этих контекстах вам по-прежнему требуется использовать постоянный размер массива даже в C99.

  2. Кто бы ни сказал, что вы распадаете 2D-массивы на 1D-массивы, было очень неправильно. То, как вы используете init_2d в своей программе, неверно: вам не разрешается самостоятельно «переинтерпретировать» 2D-массив в виде массива 1D. Любой C компилятор немедленно жалуется на

    init_2d(a, m, n); 
    

    вызова, так как этот вызов пытается передать 2D int массива аргумент для int * параметра. Это незаконно. Чтобы отключить компилятор вы должны сделать

    init_2d((int *) a, m, n); 
    

    Вполне возможно, что ваш init_2d будет «работать, как ожидалось», но до сих пор нет никаких оснований для использования хаков, как это.

    Что действительно может быть сделано здесь, это разложение 2D-массива на указатель на 1D-массив. Верьте или нет, это именно то, что вы уже делали в своей декларации print, даже если это не сразу заметно. Ваш

    void print(int x, int y, int a[x][y]) 
    

    декларация фактически эквивалентна

    void print(int x, int y, int (*a)[y]) 
    

    декларации. То есть a на самом деле является указателем на тип int [y] - указатель на массив 1D int размером y. Вышеуказанные два объявления - это всего лишь два поверхностно разных способа сказать одно и то же.

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

    void print(int x, int y, int (*a)[x][y]) 
    

    В этом случае, чтобы передать массив, который вы должны использовать & оператор

    print(m, n, &a); 
    

    и получить доступ к массиву внутри функции, которые вы должны использовать * оператор

    printf("%d  ", (*a)[i][j]); 
    
  3. нет, в вашем случае это не может быть. Невозможно пересечь встроенный 2D-массив указателем на указатель. BTW, как вы уже выяснили в своей реализации init_2d (что «работает», даже если оно незаконно), 2D-массив внутренне реализован как 1D-массив с автоматическим пересчетом индекса 2D-1D. Указатель на указатель не поможет вам пересечь такие «плоские» линейные данные.

    Типы указателей на указатели (или, в более общем смысле, многоуровневые типы указателей) часто используются при работе с совершенно разными типами многомерных массивов: когда N-мерный массив реализуется как массив указатели на независимо распределенные (N-1) -мерные массивы.Этот многомерный массив также используется довольно часто в программах на C, но его необходимо реализовать и управлять вручную (здесь вы можете найти немало примеров на SO). Между тем, как я сказал выше, встроенные языковые массивы реализованы совершенно по-другому, и они не могут пересекаться многоуровневыми указателями.

+0

Ваш ответ был очень полезен. Спасибо. У меня есть некоторые сомнения относительно вашего ответа: я могу понять, что структуру данных, которая объявлена ​​как [m] [n], не может быть пройдена с использованием двумерных массивов. Но скажу, что я объявляю указатель на указатель (** a) и выделяю ему правильное пространство, используя malloc. Тогда я могу использовать его как двумерный массив (a [i] [j]). Если он работает, то он также работает с более старыми версиями C. – Nishant

+0

Другое сомнение: если я объявляю [m] [n], то не является «a» указателем на массив с размером m x n. Тогда почему не может быть передан двумерный массив как указатель, как я сделал это в функции init_2d. – Nishant

+0

@Nishant: Как я сказал выше, можно вручную реализовать специальный вид 2D-массива с помощью указателя '(** a)'. Однако невозможно просто выделить «плоский» блок памяти для такого массива. Массив, реализованный с помощью указателя '(** a)', должен иметь двухуровневую структуру. Эта структура должна быть построена вручную. И он будет полностью отличаться от встроенного 2D-массива и несовместим с ним. См. Здесь, например: http://stackoverflow.com/questions/2842135/c-programming-malloc-for-a-2d-array-using-pointer-to-pointer – AnT

4
  1. Власе (массивы переменной длины) существуют в некоторых версиях C. VLAS позволяют выделять массив фиксированной длины в стеке во время выполнения, используя назначенные переменные, по сравнению с в куче с помощью malloc() или аналогичной функции.

  2. Я никогда не видел синтаксис объявления массива, используемый в print(), поэтому я не могу ответить на это. Обычно можно оставить скобки пустыми или использовать разложившийся указатель.

  3. Да.

4
  1. ISO C90 запрещает массив переменной длиной, но компилятор новее, так что это нормально. Это не сработает, если вы скомпилируете флаги -ansi -pedantic -Wall.
  2. Это также не сработает, если вы скомпилируете флаги -ansi -pedantic -Wall (также не подходит ISO C90). Но ваш новый компилятор в порядке.
  3. Да, вы можете использовать указатель на указатель для обхода 2d массив (На самом деле, символ ** параметр ARGV для main пример)

Похоже, что книги, которые вы читаете соответствуют к C90. Тем не менее, новые версии gcc.

0

1) m и n в вашем основном уже имеют значение, поэтому объявление массива просто отлично. Они используются как числовые константы. 2) Массив - это указатель.Таким образом,

void foo(int array[]); 
void foo(int* array); 

- точно такие же.

Массив int a [2] [2] - это 1-мерный массив с 2 указателями на int, и эти 2 указателя указывают на 2 целых числа. Таким образом, при правильной игре вокруг вашего первого указателя (в вашем случае a) вы можете получить доступ к этим целым числам.

3) посмотрите на википедию. Хороший алгоритм лучше понять.

1

Распределение памяти в массивах C является смежным, поэтому выражение a[i][j] (init_2d) может быть ассимилировано до a[i*y + j] (печать).

a в int** (2-тусклый: N х М) можно рассматривать как int* одномерный массив, а (размер: N * M) ...

Смотрите хорошие другие ответы на 1. 2 и 3.

См.: Row-major order (C-style), поэтому 2. в порядке.

Примечание: Судя по всему, что я говорю здесь, правда, только с последними версиями языка программирования и/или какой-то конкретной ситуации и абсолютно не обобщать. Поэтому лучше сначала прочитайте несколько других ответов.

3
  1. VLA.
  2. init_2d принимает int *, но вы прошли мимо него int (*)[n]. Однако, поскольку a является массивом массива, адрес a[0] совпадает с адресом a[0][0], поэтому init_2d «работает». Ваш компилятор должен был предупредить вас о несовместимости типа, когда вы прошли a до init_2d. Если это не так, вы должны пожаловаться своему поставщику компилятора.

    print использует VLA для третьего параметра.

  3. Если вы имеете в виду «Могу ли я пройти a с int **, ответ„не в прямой форме“Причина в том, что если у вас есть int **p, вы можете только реально пройти a если p инициализируется как это.:

    int a[n][m]; 
    int *ap = &a[0][0]; 
    int **p = &ap; 
    

    Но, то вы просто проходя через aap разыменовывая p. указатель на указатель, как правило, используется для обозначения массива указателей.

    int *x[10]; 
    int **p = x; 
    

    Но ваш a не является массивом указателей.

Смежные вопросы