Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

4.4.2. Статические многомерные массивы.

Большая свобода в выборе средств манипуляции элементами массивов, предо­ставляемая языками С и C++, иногда вносит путаницу в понимание основных положений, которые позволяют эффективно работать с массивами более высоких размерностей. Действительно, надо затратить некоторые усилия, чтобы осознать эквивалентность следующих выражений: аrr[2] и 2[аrr] при условии, что был объявлен массив, например double arr[6];. Или эквивалентность a[i][j] и *(*(a+i)+j) при условии, что был объявлен двухмерный массив, например int а[3][4];. Следует отметить, что можно никогда не использовать выражения типа 2[аrr] и, тем не менее, создавать работоспособные программы, но нужно уметь производить анализ выражений такого типа, чтобы понять, как компилятор ин­терпретирует переменные с индексами.

Как уже было отмечено, если объявлен одномерный массив, например float arr[6];, то имя массива аrr (без последующего индекса) может быть исполь­зовано как константный указатель на его первый элемент. Особенностью языков С и C++ является тот факт, что выражение аrr[5] трактуется компилятором как *(аrr+5). Действительно, так как аrr — адрес начала массива, то аrr+5 означает (с учетом правил адресной арифметики) адрес шестого, последнего элемента масси­ва. Следовательно, *(аrr+5) — это содержимое по адресу аrr+5, то есть равенство arr[5]==*(arr+5) истинно. Теперь проанализируем, как компилятор трактует выра­жение 5[аrr]. Сначала он преобразует 5[аrr] в *(5+аrr), после чего очевидно: 5[аrr] <==> *(5+арр) <=> *(арр+5) <=> арр[5]

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

Рассмотрим теперь двухмерный массив int a[2][3];. Встретив описание такого типа, компилятор отводит в памяти место для линейного размещения мас­сива в виде последовательности ячеек.

1-й эл-т

1-й строки

Посл. эл-т 1-й строки

1-й эл-т

2-й строки

Посл. эл-т посл. строки

а[0][0]

а[0][1]

а[0][2]

а[1][0]

а[1][1]

а[1][2]

Двухмерный массив рассматривается как массив массивов. Элементами глав­ного массива из двух элементов являются одномерные массивы, каждый из трех элементов типа int. В языке имеют смысл такие объекты:

• а[0][0] — первый элемент массива типа int;

• а[0] — адрес первого элемента массива типа int*;

• а — адрес первой строки массива типа int**.

Выражение а+1 означает адрес второй строки массива, то есть адрес, сдвину­тый на один элемент массива массивов, а таким элементом является строка двух­мерного массива. Выражение а+1 подразумевает сдвиг от а на размер одной строки (а не на размер числа типа int). Адресная арифметика всегда осуществ­ляется в единицах базового типа данных. Теперь такой единицей является стро­ка двухмерного массива или массив целых из трех элементов. Имеют место сле­дующие равенства:

а==&а[0]; а[0]==&а[0][0];

Если вывести содержимое этих адресов: а, &а[0], а[0], &а[0][0] (в формате %р), то мы обнаружим, что все они представляют собой одно и то же число, являющееся адресом первого элемента. Но, несмотря на чис­ленное равенство, объекты из первого равенства и объекты из второго равенства, например, а[0] и а, принадлежат к разным типам, и их не следует смешивать в одном выражении, так как, несмотря на численное равенство, это объекты раз­ной природы. Поучительно в режиме пошагового выполнения просмотреть сле­дующую программу:

void main()

{

int a[2][3] = { {1,2,3},

{4,5,6}};

printf("\n **а = %d\t\t a[0][0] = %d”

”\n a = %p\t a[0] = %p"

"\n &a[0]= %p\t &a[0][0]= %p"

"\n a+1 = %p\t a[0]+l = %p\n”, **a,a[0][0],a,a[0], &a[0],&a[0][0],a+l,a[0]+l);

printf("\n a == &a[0] = %d"

"\na[0]== &a[0][0] = %d\n\n", a==&a[0],a[0]==&a[0][0]);

}

Массив а[2][3] можно инициализировать в точке его определения так, как показано в примере. Первым параметром функции printf всегда является стро­ка символов. Несмотря на переносы в тексте, все части одной строки склеятся при компиляции. Результат работы первой printf занимает четыре строки на экране. Первая строка вывода будет такой:

**а=1 а[0][0]=1.

Так как а является адресом адреса, то понадобилась двойная разадресация (**а) для того, чтобы до­браться до первого элемента массива. Таким образом, выражение **а==а[0][0] является истинным. Вторая и третья строки дают одинаковые численные резуль­таты. Все числа являются адресом первого элемента массива. Они могут быть, например: 0012FF68.. Четвертая строка вывода дает два разных числа: первое — адрес первого элемента второй строки, второе — адрес второго элемента первой строки, например:

а+1 = 0012FF74 а[0]+1 = 0012FF6C

Результат приводит к заключению, что единица в выражении а+1 «весит» 12 байт, так как в строке три элемента по четыре байта (не забывайте о том, что подсчеты надо производить в шестнадцатеричной системе исчисления), а едини­ца в выражении а[0]+1 сдвигает нас от адреса первого элемента на четыре байта (один элемент типа int). Следующие две строки вывода — это единицы, что означает истинность равенств. Равенства: а==&а[0] и а[0]==&а[0][0] проходят без замечаний. Если же испытать другие два равенства: а==а[0] и а==&а[0][0], то они вызовут со стороны компилятора сообщения об ошибке.

Tenepь посмотрим, как компилятор интерпретирует выражение a[i][j]. Сначала вычис­ляется а+i, что является адресом i+1 строки. Здесь по правилам адресной ариф­метики 1 — это число байтов в i строках. Затем производится разадресация *(а+i ) и добывается (еще только) адрес первого элемента i+1 строки (отсчет от единицы) После этого вычисляется адрес элемента, сдвинутого на j единиц (уже других единиц): *(a+1)+j. Осталось разадресовать это выражение, чтобы достать элемент a[i][j]. Следовательно, a[i][j]==*(*(a+i )+j). Если задан массив int a[2][3];, то доступ к элементу а[1][1] может быть осуществлен с помощью выражения *(*(а+1)+1). Согласно правилам адресной арифметики и в предположении, что sizeof(int)=4, единицы в этом выражении будут «весить» 12 и 4 байта.