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

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

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

Однако я просто не могу узнать, как компилятор "понимает" определение типа несмотря на обычные правила приоритета оператора для * и []. Если вместо этого я должен был использовать определение типа, проблема становится значительно более простой:

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])?

Править: Конечно, вместо "преобразования типа" я имел в виду "определение типа" (это была просто опечатка).

23
задан codaddict 23 August 2010 в 03:17
поделиться

8 ответов

Во-первых, в вашем вопросе вы имеете в виду "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 s, а 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 question 1.21 .На практике, если вы читаете такое сложное объявление, что-то серьезно не так с кодом!)

19
ответ дан 29 November 2019 в 00:37
поделиться

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

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

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

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

2
ответ дан 29 November 2019 в 00:37
поделиться

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

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

2
ответ дан 29 November 2019 в 00:37
поделиться

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

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

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

void (* funcPtr) ();

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

Чтобы просмотреть, «начать с середины» («funcPtr is a ...»), пройдите вправо (там ничего нет - вас останавливает {{1 }} правая скобка), перейдите влево и найдите '*' ("... указатель на ..."), перейдите вправо и найдите пустой {{ 1}} список аргументов («... функция, которая не принимает аргументов ...»), перейдите влево и найдите пустоту («funcPtr - указатель в функцию, которая не принимает аргументов и возвращает void »).

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

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

Каждый раз, когда у вас возникают сомнения относительно сложных объявлений, вы можете использовать инструмент 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

53
ответ дан 29 November 2019 в 00:37
поделиться

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

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

int* a[3];

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

typedef int threeInts[3];
threeInts* pointerToThreeInts;
22
ответ дан 29 November 2019 в 00:37
поделиться

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

Кстати. Как только вы это усвоите, такие объявления станут проще простого :)

2
ответ дан 29 November 2019 в 00:37
поделиться

Это означает, что

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

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

6
ответ дан 29 November 2019 в 00:37
поделиться