Недавно я читал учебник Брэйна и Денниса «Язык программирования C» и обнаружил, что существует разница между определениями массива указателей, показанными ниже:
int (*a)[10];
int *a [10];
(https://i.sstatic.net/82lnWvfT.png)
В учебнике об этом написано так:
Из учебника по языку программирования C
Я не совсем мог понять разницу. Если
int (*a)[10];
определяет указатель на массив из 10 целых чисел, для чего используется '*'. И в чем разница между
int (*a)[10];
int a [10];
int (*a)[10];
— это указатель на int
массив размера 10
. int *a [10];
— это массив 10
int*
.
Это printf("%zu\n", sizeof a);
может что-то раскрыть. Первый — это размер указателя, второй — размер массива.
Полагаю, путаница возникает из-за того, что во многих ситуациях массивы распадаются на указатели. Некоторые учителя заходят так далеко, что говорят, что массив — это указатель на первый элемент, но это неправда. Массив — это массив, а указатель — это указатель.
Пожалуйста, прочтите Почему мне не следует загружать изображения текста/данных/ошибок при задании вопроса?
@NathanOliver, так в чем же разница с int a [10];
, если оба представляют собой целочисленный массив размером 10? Означает ли это, что они одинаковы?
@Abenaki Нет, они разные. int a[10]
дает вам массив из 10 int
. int (*a)[10];
— указатель на массив из 10 int
. Это не массив, но он может указывать на массив.
точно так же, как int * ptr
объявляет указатель на одиночный int
, int (*a)[10];
создает указатель на массив int
,
@NathanOliver да, теперь я понял. Когда я печатаю размеры int (*a)[10]
, они занимают всего 4 байта, а переменная int a[10]
занимает 40 байт. Спасибо за помощь.
Вот как я интерпретирую это, чтобы упростить - правило справа налево (рекурсивно). Итак, для первого случая
int (*a)[10];
а — справа ничего нет из-за закрывающих скобок. Теперь слева написано, что это указатель. До сих пор (*a) означает, что a является указателем. Теперь снова идем вправо, поэтому массив [10] затем влево типа int. Итак, чтобы завершить это — a — это указатель на массив int[10].
int *b [10];
b - справа a находится массив размером 10, а затем слева от указателей int. Итак, b — это массив int* размера 10.
Вы можете убедиться, проверив размер a и b.
На моей машине:
size of a:8
size of b:80
Большое спасибо. Это потрясающий способ запомнить их.
В этой декларации
int a [10];
объявлен массив из 10 объектов типа int
.
Вы можете проверить размер массива следующим образом
printf( "sizeof( a ) = %zu\n", sizeof( a ) );
Если sizeof( int )
равно 4
, то приведенный выше вызов printf
выведет 40
: размер всего массива.
В этой декларации
int (*a)[10];
объявлен указатель на массив элементов 10
.
Это переименовать идентификатор a
в p
, тогда вы можете написать с учетом объявления массива a
выше:
int a [10];
int ( *p )[10] = &a;
Размер указателя p
равен 4
или 8
в зависимости от используемой системы. Вы можете проверить это, используя следующий вызов printf
:
printf( "sizeof( p ) = %zu\n", sizeof( p ) ):
Разыменовав указатель, вы можете получить массив как один объект, на который указывает указатель p
. Так что если ты напишешь
printf( "sizeof( *p ) = %zu\n", sizeof( *p ) );
вы получите размер массива a
.
Что касается этой декларации
int *a [10];
затем он объявляет массив, элементы которого имеют тип указателя int *
. Было бы более читабельно переписать эту строку так:
int * a [10];
На самом деле это объявление эквивалентно
int * ( a [10] );
Опять же, вы можете использовать вызов printf
как
printf( "sizeof( a ) = %zu\n", sizeof( a ) );
И результат будет равен 40
или 80
в зависимости от того, равно ли sizeof( int * )
4
или 8
.
Чтобы объявить указатель на этот массив, вы можете написать
int * a [10];
int * ( *p )[10] = &a;
И в объявлениях, и в выражениях постфиксные []
и ()
имеют более высокий приоритет, чем унарные *
, поэтому *a[i]
анализируется как *(a[i])
— мы индексируем в a
, а затем разыменовываем результат.
Напротив, с помощью (*a)[i]
мы разыменовываем a
, а затем индексируем результат.
Некоторые примеры кода и изображения могут помочь:
int x, y, z;
int *a[3] = { &x, &y, &z };
+---+ +---+
a: | | a[0] -----> x: | |
+---+ +---+
| | a[1] ---+
+---+ | +---+
| | a[2] -+ +-> y: | |
+---+ | +---+
|
| +---+
+---> z: | |
+---+
a[0] == &x // int * == int *
*a[0] == x // int == int
Контраст с:
int v[3];
int (*a)[3] = &v;
+---+ +---+
a: | | -----> v: | | v[0]
+---+ +---+
| | v[1]
+---+
| | v[2]
+---+
a == &v // int (*)[3] == int (*)[3]
*a == v // int [3] == int [3]
Прочтите этот ответ на аналогичный вопрос. Короткий ответ: да, круглые скобки важны.