Приведем пример работы с двумерным массивом (матрицей) с использованием вспомогательного массива указателей.
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
# define N 3
# define M 2
void main()
{
float matr[N][M];
float *pmatr[N];
srand(time(NULL));
printf("Доступ с помощью индексации имени массива:\n");
for (int i = 0; i<N; i++)
{
for (int j = 0; j<M; j++)
{
matr[i][j] = float (rand())/RAND_MAX*10;
printf("%3.1f\t", matr[i][j]);
}
printf("\n");
}
printf("Доступ с использованием вспомогательного массива:\n");
for (i = 0; i<N; i++)
pmatr[i] = &matr[i][0];
for (i = 0; i<N; i++)
{
for (int j = 0; j<M; j++)
printf("%3.1f\t", *(*(pmatr+i)+j));
printf("\n");
}
}
Доступ с помощью индексации имени массива называется прямым, а с помощью указателей вспомогательного массива – косвенным. Также отметим, что введенный массив pmatr также можно индексировать, т.е. запись *(*(pmatr+i)+j)) эквивалентна записи pmatr[i][j].
Так как указатель это переменная, в которой хранится адрес другой переменной, то С++ разрешает ввести «указатель на указатель» - т.е. переменную в которой будет храниться адрес ячейки памяти, в которой записан адрес другой ячейки. Синтаксис объявления «указателя на указатель» следующий:
|
|
тип **имя_указателя;
Следующий пример демонстрирует, как получить доступ к переменной, используя двойное разъименовывание указателя.
# include <stdio.h>
void main()
{
int A = 5; //переменная А
int *pA; //указатель на переменную А
int **ppA; //указатель на указатель pA
pA = &A; //получаем адрес переменной А
ppA = &pA; //получаем адрес указателя pA
printf("A = %d\n", **ppA);// получаем значение переменной А
}
Аналогично можно ввести «указатель на указатель на указатель» и т.д. Применяются они при работе с многомерными массивами.
# include <stdio.h>
void main()
{
int a = 10, b = 20, c = 30; //набор переменных
int * pM[3]; //массив указателей
pM[0] = &a; //записываем
pM[1] = &b; //в массив
pM[2] = &c; //адреса переменных a, b, c
int **ppM; //указатель на массив указателей
ppM = pM; //вспоминаем, что имя массива – адрес его
//первого элемента
//печатаем адреса переменных a, b, c хранящиеся в массиве pM
//и значения этих переменных, полученные с помощью указателя ppM
for (int i = 0; i<3; i++)
printf("&pM[%d] - %p\t%d\n", i, &pM[i], *(*(ppM+i)));
}
Результат работы программы:
&pM[0] - 0012FF68 10
&pM[1] - 0012FF6C 20
&pM[2] - 0012FF70 30
Заметим, что выражение *(*(ppM+i)) можно заменить эквивалентным, но более простым для понимания ppM[i][0].
Теперь рассмотрим, как можно разместить целочисленную матрицу размером N на M в динамической памяти. Алгоритм действия следующий.
1. Объявляем указатель на массив указателей int **pMatr;.
2. Выделяем память для хранения N указателей на первые элементов в каждой строке pMatr = new int * [N];.
3. В цикле выделяем память для хранения строк матрицы. Фактически эти строки можно рассматривать как одномерные массивы из M элементов.
|
|
for (i = 0; i<N; i++)
pMatr[i] = new int[M]; //в строке М элементов
Рисунок 6.1 – Схема имитации двухмерного динамического массива с помощью массива указателей и набора одномерных массивов.
Приведем программу, которая запрашивает размер матрицы и размещает ее в динамической памяти.
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
void main()
{
int i, j, N, M;
printf("N = ");
scanf("%d", &N);
printf("M = ");
scanf("%d", &M);
int **pMatr;
pMatr = new int * [N];
for (i = 0; i<N; i++)
pMatr[i] = new int[M];
srand(time(NULL));
for (i = 0; i<N; i++)
{
for (j = 0; j<M; j++)
{
*(*(pMatr+i)+j) = rand()%11;
printf("%d\t", pMatr[i][j]);
}
printf("\n");
}
}
Еще раз обратим внимание на эквивалентность записей *(*(pMatr+i)+j) и pMatr[i][j]. Каждый может пользоваться формой, которая ему больше понятна.
Список рекомендуемой литературы.
1 Уэйт М., Прата С., Мартин Д. Язык Си. – М.: Мир, 1998.
2 Либерти Д. С++ за 21 день. – М.: Вильямс, 2000.
3 Подбельский В.В., Язык СИ++. – М.: Финансы и статистика, 2000.
4 Майкл Дж. Янг, Visual C++ 6, - Киев: «Ирина», 1999.