Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЗФ_ОАиП / Лекции ГГУ Скорины - Программирование.doc
Скачиваний:
179
Добавлен:
21.03.2016
Размер:
2.27 Mб
Скачать

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

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

Обратить внимание! Как и для одномерных массивов, выражение a[i][j] в языке C всегда преобразуется к виду: *(адрес начала массива + смещение), где смещение определяется уже двумя индексами i и j. Т.е. доступ к элементу двумерного массива в языке С физически реализуется опять же через адрес начала массива в памяти и смещения элемента (индексы элемента) от начала массива.

Как уже говорилось, двумерный массив – это массив массивов, т.е. такой массив, элементами которого являются массивы. Можно считать, что двумерный массив mas[n][m] – это n последовательно расположенных одномерных массивов размерностью m.

Как определить смещение элемента mas[i][j]? Надо пропусть i раз по m элементов + еще j элементов. Как узнать адрес начала массива? Его должно быть можно определить по имени массива. Тогда самый очевидный способ доступа к элементу двумерного массива с помощью указателей такой:

int mas[2][3];

int *p = (int *)mas; // необходимо преобразование типа, т.к. по смыслу

// определения двумерного массива как массива

// массивов mas имеет тип int[3] *

// p = &mas[0][0]; - можно и так определить адрес начала массива

int x = *(p + 1*3 + 2); // x = mas[1][2]

Но так получать доступ к элементам двумерного массива не очень удобно.

С другой стороны, по определению каждая строка двумерного массива – одномерный массив. И, скорее всего, можно стать на нужный элемент, отталкиваясь от адреса соответствующей строки (и это было бы очень удобно). Логично предположить, что mas[i] будет адресом начала i-ой строки двумерного массива. Но тогда mas должно быть адресом массива указателей на строки двумерного массива.

Получаем, что с одной стороны mas должно быть указателем-константой на начало массива, а с другой стороны имя двумерного массива должно быть указателем-константой на массив указателей-констант, а элементами массива указателей должны быть указатели-константы на начало каждой из строк массива (одномерный массив).

Вывод: когда в программе объявляется массив в виде int mas[2][3], то этим определяется не только выделение памяти для шести элементов массива, но еще и определяется массив указателей-констант из 2-х элементов mas[0] и mas[1], значениями которых являются адреса соответствующих строк массива, а также определяется указатель-константа с именем mas, значение которого равно адресу массива указателей-констант с адресами строк массива.

Пусть у нас есть двумерный массив: int mas[2][3];

указатель-константа

Для нашего двумерного массива int mas[2][3]; указателями-константами на нулевую и первую строки будут mas[0] и mas[1], а следующие выражения будут тождественными:

mas[0] == &mas[0][0]

mas[1] == &mas[1][0]

Доступ к элементам массива указателей осуществляется с указанием одного индексного выражения в форме: mas[1] или *(mas+1) – и получим сразу адрес первой строки (т.е. сместимся от начала массива на одну строку == mas + 1*3).

Для доступа к элементам двумерного массива используются два индексных выражения в форме: mas[1][2], *(mas[1]+2) или *(*(mas+1)+2) (можно и так написать (*(mas+1))[2]). Т.е сместимся от начала массива на одну строку, взяв адрес этой строки из указателя-константы, а затем сместимся уже в нужной строке на нужное количество элементов. Так мы получим адрес нужного элемента, а операция * даст нам значение этого элемента.

Получить адрес самого первого элемента массива (с индексами [0][0]) можно так:

int mas[2][3];

int *p = (int *)mas;

int *p1 = &mas[0][0];

int *p2 = mas[0];

Получить значение самого первого элемента массива (с индексами [0][0]) можно так:

int xx;

xx = *p;

xx = **mas; // *(*(m+0)+0)

xx = mas[0][0]; // xx = *p1;

xx = *mas[0]; // xx = *p2;

Рассмотрим вывод двумерного массива разными способами.

int i, j;

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

{4,5,6}

};

for (i = 0; i < 2; i++) {

for (j = 0; j < 3; j++)

printf("%4d ", *(x[i]+j));

printf("\n");

}

=== или можно так ========================

for (i = 0; i < 2; i++) {

for (j = 0; j < 3; j++)

printf("%4d ", *(*(x+i)+j));

printf("\n");

}

=== или можно так ========================

int *px = (int *)x; // имя массива – адрес его самой первой строки

for (i = 0; i < 2; i++) {

for (j = 0; j < 3; j++)

printf("%4d ", *(px + 3*i + j));

printf("\n");

}

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

void main() {

int a[10][10], n, i, j;

int max, max_d;

do {

printf("Введите размерность матрицы n = ");

scanf("%d", &n);

}

while (n < 0 || n > 10);

printf(“Введите матрицу\n”);

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

for (j = 0; j < n; j++)

scanf("%d", *(a + i) + j);

printf(“Вы ввели матрицу\n”);

for (i = 0; i < n; i++) {

for (j = 0; j < n; j++)

printf("%4d ", *(*(a + i) + j));

printf(“\n”);

}

max = **a;

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

for (j = 0; j < n; j++)

if (max < *(*(a + i) + j))

max = *(*(a + i) + j);

printf(“Максимальный элемент матрицы = %d\n”, max);

max_d = **a;

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

if (max_d < *(*(a + i) + i))

max_d = *(*(a + i) + i);

printf(“Максимум на диагонали матрицы = %d\n”, max_d);

}

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

int a[10][20];

int *b[10];

записи a[5][7] и b[5][7] будут синтаксически правильным обращением к некоторому значению типа int. Однако только a является классическим двумерным массивом: для двухсот элементов типа int будет выделена память, а вычисление смещения элемента a[строка][столбец] от начала массива будет вестись по формуле 20 * строка + столбец, учитывающей его прямоугольную природу. Для b же определено только 10 указателей, причем без инициализации. Инициализация должна задаваться явно – либо статически, либо в программе. Предположим, что каждый элемент b указывает на массив из 20 элементов, в результате где-то будет выделено пространство, в котором разместятся 200 значений типа int, и еще 10 ячеек будет выделено для указателей.

Важное преимущество массива указателей в том, что элементы такого массива могут иметь разные длины. Таким образом, каждый элемент массива b не обязательно указывает на массив из 20 элементов; один может указывать на два элемента, другой — на пятьдесят, а некоторые и вовсе могут ни на что не указывать.

Задача. Что увидим на экране?

void main() {

int xx[3][3] = { {1,1,1}, // 1 строка вывода

{2,2,2}, // 3 строка вывода

{3,3,3}

};

int yy[3][3] = { {5,5,5}, // 4 строка вывода

{7,7,7},

{9,9,9} // 2 строка вывода

};

// === вроде как массив [4][3] ======================

int *x[] = {xx[0], yy[2], xx[1], yy[0], NULL};

int i=0;

while (x[i]) {

printf("%d %d %d\n", *x[i], *(x[i]+1), *(x[i]+2));

// printf("%d %d %d\n", x[i][0], x[i][1], x[i][2]);

i++;

}

}

Задача: найти максимум в двух массивах xx и yy. Можно искать два максимума и выбирать больший из них. А можно аналогичным образом составить один массив и искать максимум в одном массиве.