Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции_СП_2004_1_00.doc
Скачиваний:
69
Добавлен:
04.11.2018
Размер:
882.69 Кб
Скачать

3. Указатели на массивы. Массивы указателей и многомерные массивы

Введенное в предыдущем параграфе понятие указателя на простую переменную естественным образом распространяется на любые структурированные типы данных. В частности, декларация

float (*vector)[15];

определяет имя vector как указатель на массив пятнадцати элементов типа float, причем круглые скобки в этой записи являются существенными. Обращение к i-ому элементу такого массива будет выглядеть следующим образом:

(*vector)[i]

Определяя указатель на массив, мы сохраняем все преимущества работы с указателями и, кроме того, требуем от компилятора выделить реальную память для размещения элементов этого массива. Так как сами по себе указатели являются переменными, то нетрудно построить ограниченный вектор элементов-указателей на некоторый базовый тип данных. Такие структуры данных в языке Си принято называть массивами указателей. Их описание строится на той же синтаксической основе, что и описание обычных массивов. Например, инструкция

char *text[300];

определяет массив трехсот указателей на элементы данных типа char. Поскольку каждый отдельный элемент этого массива может хранить адрес начала некоторой цепочки символов, то после фактического выделения памяти под размещение трехсот таких цепочек и присвоения адреса каждой из них определенному элементу массива, весь массив указателей будет задавать набор соответствующего количества строк переменной, вообще говоря, длины. Элементы массива указателей могут быть инициализированы подобно тому, как инициализировались отдельные указатели и обычные массивы:

char *week[] = { "Понедельник",

"Вторник",

"Среда",

"Четверг",

"Пятница",

"Суббота",

"Воскресенье" };

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

type-specifier identifier[const-expression][const-expression];

где все обозначения использованы в том же смысле, что и ранее. Так, описание

char table[10][20];

определяет массив десяти массивов, каждый из которых содержит по двадцать элементов типа char. Легко заметить, что это есть ни что иное, как синоним двумерного массива, причем первый индекс определяет номер строки, а второй - номер столбца. Очевидно, что желая сохранить тесную связь массивов и указателей, следует потребовать, чтобы двумерные массивы размещались в памяти ЭВМ по строкам, отождествив имя массива с адресной ссылкой &table[0][0]. Обращение же к индивидуальным элементам двумерного массива осуществляется, как и в случае одного измерения, посредством индексных выражений. Отличие массива указателей от массива массивов состоит, главным образом, в том, что в первом случае резервируются лишь ячейки памяти для хранения адресов строк двумерной таблицы, в то время как реальная память под размещение элементов каждой строки не выделяется. Во втором же случае полностью определен объем памяти, занимаемой всей таблицей. С другой стороны, общее сходство между двумя этими структурами данных позволяет работать с массивами указателей точно так же, как и с двумерными массивами, используя, например, двойную индексацию

week[2][3]

для выделения четвертого по счету символа в третьей строке, и наоборот, рассматривая ссылку вида

table[i]

как адрес нулевого элемента i-ой строки таблицы table. Такое соглашение выглядит достаточно естественным, если вместо термина "многомерный массив" всюду использовать понятие "массив массивов". Проведенная аналогия между массивами указателей и массивами массивов дает возможность придать вполне конкретный смысл выражению вида

table[i] + k

задающему адрес k-ого элемента i-ой строки массива table, который в терминах операции взятия адреса определяется как

&table[i][k]

Поэтому наряду с традиционной ссылкой

table[i][k]

назначение элемента (i, k) этого массива можно пользоваться эквивалентной ей ссылкой

*(table[i] + k)

на языке указателей. Далее вспоминая, что имя всякого массива при отсутствии индексных выражений отождествляется с адресом его самого первого элемента, нетрудно понять, что выражение

table + j

является обычным адресным выражением, определяющим размещение в памяти нулевого элемента j-ой строки таблицы table. Нетрудно заметить, что несмотря на общность свойств, массивы указателей обеспечивают возможность более гибкого манипулирования данными, нежели многомерные массивы. Дальнейшее увеличение гибкости структур данных связано с понятием косвенного указателя или " указателя на указатель", который может быть определен следующим образом:

<sc_specifier> type-specifier **identifier <, ... >;

Здесь вновь сохраняется аналогия с рассмотренными выше объектами, т. е. такое описание окажется полностью равносильным двумерному массиву после того, как будет выделена реальная память под хранение адресов его строк и размещение элементов каждой отдельной строки. Это можно сделать, используя, например, функцию malloc() или alloca() (см. $ 4 настоящей лекции):

double **dataptr;

dataptr = (double**)alloca(m*sizeof(double*));

for (i = 0; i < m; i++)

dataptr[i] = (double*)alloca(n*sizeof(double));

В последнем примере осуществляется размещение в памяти ЭВМ двумерного массива размера m*n элементов типа double.

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