Интерпретация int (* a) [3] - PullRequest
       11

Интерпретация int (* a) [3]

22 голосов
/ 12 февраля 2010

При работе с массивами и указателями в C быстро обнаруживается, что они ни в коем случае не эквивалентны, хотя это может показаться на первый взгляд. Я знаю о различиях в L-значениях и R-значениях. Тем не менее, недавно я попытался выяснить тип указателя, который мог бы использовать в сочетании с двумерным массивом, то есть

int foo[2][3];
int (*a)[3] = foo;

Однако я просто не могу выяснить, как компилятор «понимает» определение типа a, несмотря на правила приоритета обычного оператора для * и []. Если бы вместо этого я использовал typedef, проблема стала бы значительно проще:

int foo[2][3];
typedef int my_t[3];
my_t *a = foo;

В заключение, может ли кто-нибудь ответить мне на вопросы о том, как компилятор читает термин int (*a)[3]? int a[3] прост, int *a[3] также прост. Но тогда почему это не int *(a[3])?

РЕДАКТИРОВАТЬ: Конечно, вместо "typecast" я имел в виду "typedef" (это была просто опечатка).

Ответы [ 8 ]

53 голосов
/ 15 апреля 2010

Каждый раз, когда у вас возникают сомнения относительно сложных объявлений, вы можете использовать инструмент cdecl в Unix-подобных системах:

[/tmp]$ cdecl
Type `help' or `?' for help
cdecl> explain int (*a)[10];
declare a as pointer to array 10 of int

EDIT:

Также доступна онлайн-версия этого инструмента здесь .

Спасибо Тибериу Ана и GF

22 голосов
/ 15 апреля 2010

Он объявляет указатель на массив 3 int с.

Скобки необходимы, так как следующее объявляет массив из 3 указателей на int:

int* a[3];

Вы получаете лучшую читаемость при использовании typedef:

typedef int threeInts[3];
threeInts* pointerToThreeInts;
18 голосов
/ 12 февраля 2010

Во-первых, вы имеете в виду «typedef», а не «typecast» в вашем вопросе.

В C указатель на тип T может указывать на объект типа T:

int *pi;
int i;
pi = &i;

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

int a[3];
int *pa = a;

Но ради полноты: в присваивании имя a эквивалентно &a[0], то есть указателю на первый элемент массива a. Если вы не уверены, как и почему это работает, существует множество ответов, объясняющих, когда именно имя массива «распадается» на указатель, а когда нет:

Я уверен, что на SO есть еще много таких вопросов и ответов, я только что упомянул некоторые, которые я нашел из поиска.

Вернуться к теме: когда у нас есть:

int foo[2][4];

foo имеет тип "массив [2] массива [3] из int". Это означает, что foo[0] - это массив 3 int с, а foo[1] - это массив 3 int с.

Теперь предположим, что мы хотим объявить указатель, и мы хотим присвоить его foo[0]. То есть мы хотим сделать:

/* declare p somehow */
p = foo[0];

Вышеуказанное не отличается по форме от строки int *pa = a;, поскольку типы a и foo[0] одинаковы. Итак, нам нужно int *p; как наше объявление p.

Теперь, главное, что нужно помнить о массивах, это то, что «правило» о том, что имя массива уменьшается до указателя на его первый элемент, применяется только один раз. Если у вас есть массив массива, то в контексте значений имя массива не будет уменьшаться до типа «указатель на указатель», а скорее на «указатель на массив». Возвращаясь к foo:

/* What should be the type of q? */
q = foo;

Имя foo выше - это указатель на первый элемент foo, то есть мы можем написать выше как:

q = &foo[0];

Тип foo[0] - это «массив [3] из int». Поэтому нам нужно, чтобы q был указателем на "массив [3] из int":

int (*q)[3];

Скобки вокруг q необходимы, потому что [] связывается более плотно, чем * в C, поэтому int *q[3] объявляет q как массив указателей, и мы хотим указатель на массив. int *(q[3]) сверху эквивалентно int *q[3], то есть массиву из 3 указателей на int.

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

О чтении объявлений в целом: вы читаете их «наизнанку», начиная с имени «переменной» (если она есть). Вы идете влево как можно дальше, если только справа нет [], и вы всегда соблюдаете скобки. cdecl должен быть в состоянии помочь вам до такой степени:

$ cdecl
cdecl> declare p as  pointer to array 3 of int
int (*p)[3]
cdecl> explain int (*p)[3]
declare p as pointer to array 3 of int

Читать

int (*a)[3];

      a            # "a is"
    (* )           # parentheses, so precedence changes.
                   # "a pointer to"
        [3]        # "an array [3] of"
int        ;       # "int".

Для

int *a[3];

     a             # "a is"
      [3]          # "an array [3] of"
    *              # can't go right, so go left.
                   # "pointer to"
int      ;         # "int".

Для

char *(*(*a[])())()

          a         # "a is"
           []       # "an array of"
         *          # "pointer to"
        (    )()    # "function taking unspecified number of parameters"
      (*        )   # "and returning a pointer to"
                 () # "function"
char *              # "returning pointer to char"

(Пример из c-faq вопрос 1.21 . На практике, если вы читаете такое сложное объявление, в коде что-то серьезно не так!)

6 голосов
/ 15 апреля 2010

Это значит

объявить как указатель на массив 3 из INT

См. cdecl для дальнейшего использования.

4 голосов
/ 12 февраля 2010

Компилятору, вероятно, легче интерпретировать, чем вам. Язык определяет набор правил для того, что составляет допустимое выражение, и компилятор использует эти правила для интерпретации таких выражений (может показаться сложным, но компиляторы были изучены подробно, и существует множество стандартизированных алгоритмов и инструментов, вы можете захотеть читать о разборе ). cdecl - это инструмент, который будет переводить выражения C на английский язык. Исходный код доступен на веб-сайте, поэтому вы можете проверить его. В книгу «Язык программирования C ++» включен пример кода для написания такой программы, если я не ошибаюсь.

Что касается интерпретации этих выражений самостоятельно, я нашел лучший способ сделать это в книге «Мышление на С ++», том 1:

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

void (* funcPtr) ();

Когда вы смотрите на комплекс определение, как это, лучший способ Атака это начать в середине и проработайте свой выход. «Начиная с середина »означает, начиная с имя переменной, которая является funcPtr. «Выходить из себя» означает искать вправо для ближайшего пункта (ничего в этом случае; право скобка останавливает вас) глядя налево (указатель обозначен звездочкой), затем глядя на право (пустой список аргументов указывая на функцию, которая не принимает аргументы), то смотришь налево (пусто, что указывает на функцию не имеет возвращаемого значения). это движение вправо-влево-вправо работает с большинство объявлений.

Для обзора «начни с середины» («FuncPtr - это ...»), идите направо (ничего там - вы остановлены правая скобка), перейдите влево и найти ‘*’ («... указатель на ...»), идите направо и найдите пустое список аргументов («... функция, которая не принимает аргументов ... »), перейти к налево и найдите пустоту («funcPtr является указатель на функцию, которая не принимает аргументы и возвращает недействительными »).

Вы можете бесплатно скачать книгу на Сайт загрузки Брюса Экеля . Давайте попробуем применить его к int (*a)[3]:

  1. Начните с имени в середине, в данном случае a. Пока это звучит так: «а есть ...»
  2. Идите направо, вы сталкиваетесь с закрывающей скобкой, останавливая вас, поэтому вы идете налево и находите *. Теперь вы читаете: «a - это указатель на ...»
  3. Идите направо и вы найдете [3], что означает массив из 3 элементов. Выражение становится следующим: «a - указатель на массив из 3 ...»
  4. Наконец, вы идете налево и находите int, так что, наконец, вы получаете «a - указатель на массив из 3-х целых чисел».
2 голосов
/ 15 апреля 2010

В ситуациях, когда вы не знаете, для чего используется декларация, я нашел очень полезным так называемое Право-левое правило .

КСТАТИ. Как только вы это узнаете, такие декларации станут миром пирога:)

2 голосов
/ 12 февраля 2010

Вы можете найти эту статью полезной:

Я сделал этот ответ в вики сообщества (так как это просто ссылка на статью, а не ответ), если кто-то хочет добавить к ней дополнительные ресурсы.

2 голосов
/ 12 февраля 2010

Почему что означает "не int *(a[3])" в точности (имеется в виду ваш последний вопрос)? int *(a[3]) соответствует обычному int *a[3]. Брекеты избыточны. Это массив из 3 указателей на int, и вы сказали, что знаете, что это значит.

int (*a)[3] - это указатель на массив 3 int (т.е. указатель на тип int[3]). Брекеты в этом случае важны. Вы сами предоставили правильный пример, который делает эквивалентное заявление через промежуточный typedef.

Простое популярное правило чтения декларации C наизнанку в этом случае работает отлично. В объявлении int *a[3] мы должны начать с a и сначала идти вправо, то есть получить a[3], то есть a - это массив из 3 что-то (и т. Д.) , () в int (*a)[3] меняет это, поэтому мы не можем идти правильно и должны начинать с *a вместо этого: a является указателем на что-то . Продолжая декодировать декларацию, мы придем к выводу, что что-то является массивом из 3 int.

В любом случае, найдите хороший учебник по чтению объявлений на C, которых в сети довольно много.

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