При работе с массивами и указателями в 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])
?
Править: Конечно, вместо "преобразования типа" я имел в виду "определение типа" (это была просто опечатка).
Во-первых, в вашем вопросе вы имеете в виду "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 .На практике, если вы читаете такое сложное объявление, что-то серьезно не так с кодом!)
Почему то, что "не 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, которых довольно много в сети.
Вы можете найти эту статью полезной:
Я сделал этот ответ вики сообщества (поскольку это просто ссылка на статью, а не ответ), если кто-нибудь хочет добавить к нему дополнительные ресурсы.
Возможно, компилятору легче интерпретировать, чем вам. Язык определяет набор правил того, что составляет допустимое выражение, и компилятор использует эти правила для интерпретации таких выражений (может показаться сложным, но компиляторы были подробно изучены, и существует множество стандартизированных алгоритмов и инструментов, вы можете захотеть читайте о парсинге ). cdecl - это инструмент, который переводит выражения C на английский язык. Исходный код доступен на веб-сайте, так что вы можете его проверить. В книге «Язык программирования C ++», если я не ошибаюсь, был включен пример кода для написания такой программы.
Что касается интерпретации этих выражений самостоятельно, я нашел лучший способ сделать это в книге «Мышление в C ++, том 1»:
Чтобы определить указатель на функцию, которая не имеет аргументов и возвращаемого значения, вы говорите:
void (* funcPtr) ();
Когда вы смотрите на такое сложное определение, как это, лучший способ атаки - это начать с середины {{1} } и работайте над своим выходом. «Начало в середине» означает начало с имени переменной , которым является funcPtr. «Работать над своим выходом» означает искать вправо ближайший элемент (в данном случае ничего; правая скобка не дает вам остановиться), затем смотрит влево (указатель, обозначенный звездочкой), затем смотрит вправо (пустой список аргументов , указывающий на функцию, которая не принимает { {1}} аргументы), затем влево (void, что означает, что функция не имеет возвращаемого значения). Это движение вправо-влево-вправо работает с большинством объявлений.
Чтобы просмотреть, «начать с середины» («funcPtr is a ...»), пройдите вправо (там ничего нет - вас останавливает {{1 }} правая скобка), перейдите влево и найдите '*' ("... указатель на ..."), перейдите вправо и найдите пустой {{ 1}} список аргументов («... функция, которая не принимает аргументов ...»), перейдите влево и найдите пустоту («funcPtr - указатель в функцию, которая не принимает аргументов и возвращает void »).
Вы можете бесплатно скачать книгу Брюса Эккеля на сайте загрузок . Попробуем применить его к int (* a) [3]
:
Каждый раз, когда у вас возникают сомнения относительно сложных объявлений, вы можете использовать инструмент 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
Он объявляет указатель на массив из 3 int
сек.
Скобки необходимы, так как следующее объявляет массив из 3 указателей на int
:
int* a[3];
Вы улучшаете читаемость при использовании typedef
:
typedef int threeInts[3];
threeInts* pointerToThreeInts;
В ситуациях, когда вы не знаете, что такое объявление, я нашел очень полезным так называемое Правило право-лево .
Кстати. Как только вы это усвоите, такие объявления станут проще простого :)
Это означает, что
объявить a как указатель на массив 3 из int
См. cdecl для дальнейшего использования.