Студенту >> Язык программирования Си


Указатели и двумерные массивы

Пусть имеются следующие определения массивов и указателей:

int A[4][2], B[2];
int *p, (*pA)[4][2], (*pAstr)[2]; 

Здесь A представляет собой двумерный массив из четырех строк и двух столбцов, B - одномерный массив из двух элементов. Для каждого из этих массивов будет выделено соответствующее количество памяти, достаточное для хранения всех их элементов.

Указатель p представляет собой указатель на величину int, указатель pA - указатель на двумерный массив из четырех строк и двух столбцов, pAstr - указатель на одномерный массив из двух элементов. Все указатели имеют размер, равный размеру адреса для данных в используемой модели памяти. Память для хранения данных, естественно, не выделяется. Количество элементов данных из описания массивов будет использовано лишь для корректного изменения значения указателя при выполнении над ним допустимых арифметических операций.

Смысл трактовки этих указателей определяется направлением слева-направо для подряд следующих операций [], а также изменением приоритета операции * с помощью круглых скобок. Если не поставить круглых скобок, то следующее определение

int *pa[4][2];

рассматривается как определение двумерного массива из указателей на тип int.

Для вышеописанных указателей допустимы следующие операции присваивания, поскольку слева и справа от операции присваивания находятся указатели на один и тот же тип данных:

p = B;
p = &B[1];
p = &A[0][0];
p = A[2]; 

Следующее присваивание:

p = A; /* неверно */ 

является неверным, так как слева от операции присваивания находится указатель на тип int, а справа - указатель на первый элемент массива A, который (элемент) представляет собой массив из двух элементов типа int. В таких случаях компиляторы выдают предупреждающее сообщение о подозрительном преобразовании указателя.

Если программист уверен в своих действия, то он может использовать операцию явного приведения типа для устранения этого сообщения, но при этом компилятор снимает с себя всякую ответственность за корректность использования такого указателя. Так, после присваивания

p = (int *) A;

элементы, на которые ссылается указатель, и элементы массива A находятся в следующем соответствии:

p[0] эквивалентно A[0][0]
p[1] эквивалентно A[0][1]
p[2] эквивалентно A[1][0]
p[3] эквивалентно A[1][1]
p[4] эквивалентно A[2][0]
p[5] эквивалентно A[2][1]
p[6] эквивалентно A[3][0]
p[7] эквивалентно A[3][1]  

Совершенно корректными являются следующие присваивания

pAstr = A;

после которого использование массива A и указателя pAstr совершенно эквивалентны: pAstr[i][j] эквивалентно A[i][j]

Присваивание

pAstr = &A[2];  

устанавливает следующее соответствие между элементами, на которые ссылается указатель pAstr и элементами массива A:

pAstr[0][0] эквивалентно A[2][0]
pAstr[0][1] эквивалентно A[2][1]
pAstr[1][0] эквивалентно A[3][0]
pAstr[1][1] эквивалентно A[3][1] 

Следующие присваивания корректны

pA = &A; /* Указатель на двумерный массив */
pAstr = &B; /* Указатель на одномерный массив */ 

и устанавливают следующее соответствие элементов:

(*pA)[i][j] эквивалентно A[i][j]
(*pAstr)[i] эквивалентно B[i] 

Массивы указателей удобны для хранения символьных строк:

char *str[] = {
"Строка 1",
"Строка 2",
"Длинная строка 3"
};

В этом случае каждый элемент массива представляет собой адрес соответствующей строки символов, а сами строки располагаются компилятором в статическом сегменте данных. Никакой лишней памяти, связанной с различной длиной строк, при этом не расходуется.

НАВЕРХ