Интерпретация int (* a) [3]
При работе с массивами и указателями на 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])
?
EDIT: Конечно, вместо "typecast" я имел в виду "typedef" (это была просто опечатка).
Ответы
Ответ 1
Во-первых, вы имеете в виду "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
имеет тип "array [2]
массива [3]
of int
". Это означает, что foo[0]
представляет собой массив из 3 int
s, а foo[1]
- массив из 3 int
s.
Теперь скажем, мы хотим объявить указатель, и мы хотим назначить его 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]
of int
". Поэтому нам нужно q
быть указателем на "массив [3]
of int
":
int (*q)[3];
Скобки вокруг q
необходимы, потому что []
связывается сильнее, чем *
в C, поэтому int *q[3]
объявляет q
как массив указателей, и нам нужен указатель на массив. int *(q[3])
, сверху, эквивалентен int *q[3]
, т.е. массив из 3 указателей на int
.
Надеюсь, что это поможет. Вы также должны прочитать C для smarties: массивы и указатели для действительно хорошего учебника по этой теме.
О чтении деклараций в целом: вы читаете их "наизнанку", начиная с имени "переменной" (если есть). Вы уходите влево как можно больше, если только есть []
прямо справа, и вы всегда считаете круглые скобки. 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. На практике, если вы читаете такое сложное выражение, есть что-то серьезно неправильное с код!)
Ответ 2
Каждый раз, когда вы сомневаетесь в сложных объявлениях, вы можете использовать инструмент cdecl в системах Unix:
[/tmp]$ cdecl
Type `help' or `?' for help
cdecl> explain int (*a)[10];
declare a as pointer to array 10 of int
EDIT:
Существует также онлайн-версия этого инструмента здесь.
Благодаря Tiberiu Ana и gf
Ответ 3
Он объявляет указатель на массив из 3 int
s.
Скобки необходимы, поскольку следующее объявляет массив из 3 указателей на int
:
int* a[3];
Вы получаете лучшую читаемость при использовании typedef
:
typedef int threeInts[3];
threeInts* pointerToThreeInts;
Ответ 4
Это означает
объявить как указатель на массив 3 из ИНТ
Смотрите cdecl для справок в будущем.
Ответ 5
Скорее всего, проще компилятор интерпретировать, чем вам. Язык определяет набор правил для того, что представляет собой правильное выражение, и компилятор использует эти правила для интерпретации таких выражений (может показаться сложным, но компиляторы изучены по длине и существует множество стандартизованных алгоритмов и инструментов, возможно, вы захотите читать разбор). cdecl - это инструмент, который будет транслировать выражения C на английский язык. Исходный код доступен на веб-сайте, чтобы вы могли его проверить. В книге "Язык программирования С++" был использован пример кода для написания такой программы, если я не ошибаюсь.
Что касается интерпретации этих выражений самостоятельно, я нашел лучший способ сделать это в книге "Думая на С++ том 1:
Чтобы определить указатель на функцию, у которой нет аргументов и нет возвращаемого значения, вы говорите:
void (* funcPtr)();
Когда вы смотрите на сложный определение, как это, лучший способ атака начинается с середины и работать свой выход. "Начиная с средний" означает начало с имя переменной, которое является funcPtr. "Работать свой выход" означает поиск направо для ближайшего пункта (ничего в этом случае; скобки прекращают вас), то глядя влево (указатель, обозначенный по звездочке), затем глядя на right (список пустых аргументов с указанием функции, которая не принимает аргументы), затем глядя влево (void, что указывает на функцию не имеет возвращаемого значения). Эта движение вправо-влево-вправо работает с большинство объявлений.
Чтобы просмотреть "начало посередине", ( "funcPtr - это..." ), перейдите вправо (ничего там - вы остановились правая скобка), идите влево и найдите "* ("... указатель на... "), пойдите направо и найдите пустое список аргументов ("... функция, которая не принимает аргументов... "), перейдите к left и найдите пустоту (" funcPtr - это указатель на функцию, которая не принимает аргументы и возвращает void ").
Вы можете скачать книгу бесплатно на сайте загрузки Брюса Экеля. Попробуйте применить его к int (*a)[3]
:
- Начните с названия в середине, в этом случае a. Пока он читает: "a is..."
- Идите вправо, вы сталкиваетесь с закрывающейся скобкой, которая останавливает вас, поэтому вы идете влево и находите *. Теперь вы читаете его: "a - указатель на..."
- Идите направо, и вы найдете [3], что означает массив из трех элементов. Выражение принимает вид: 'a является указателем на массив из 3...'
- Наконец, вы идете влево и находите int, поэтому, наконец, вы получаете "a - указатель на массив из 3-х целых чисел".
Ответ 6
Почему именно "не 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-х (и так далее). ()
in int (*a)[3]
меняет это, поэтому мы не можем идти вправо и начинать с *a
вместо: a
- это указатель на что-то. Продолжая расшифровать декларацию, мы придем к выводу, что это что-то вроде массива 3 int
.
В любом случае, найдите хороший учебник по чтению объявлений C, из которых в Сети доступно немало доступных.
Ответ 7
Вы можете найти эту статью полезной:
Я сделал ответ на wiki сообщества (так как это просто ссылка на статью, а не ответ), если кто-то хочет добавить к ней дополнительные ресурсы.
Ответ 8
В ситуациях, когда вы не знаете, что такое объявление для, я нашел очень полезным, так называемый Правило справа.
BTW. После того, как вы это узнали, такие заявления - это торт:)