Подтвердить что ты не робот

Интерпретация 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" (это была просто опечатка).

4b9b3361

Ответ 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, из которых в Сети доступно немало доступных.

Ответ 8

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

BTW. После того, как вы это узнали, такие заявления - это торт:)