double arrow
Статический массив указателей

Статический массив указателей, например, на объекты типа int размерности 5, объявляется следующим образом:

const n=5; int * ap[n];

Память для такого массива выделяется на этапе компиляции. При таком объявлении массив, который называется ap, состоит из n элементов, каждый из которых имеет тип int *. Другими словами, ap[i], где i=0, 1, 2, …, n-1, представляет собой не целое обрабатываемое в программе число (оценку студента, план выпуска продукции и т. п.), а адрес ячейки оперативной памяти, в которой будет храниться целое число. Можно использовать операцию разыменования “*”. Содержимое ячейки памяти, адрес которой находится в ap[i], обозначается в программе *ap[i].

По аналогии с обычными указателями, i-й элемент объявленного таким образом массива указателей ap[i] можно проинициальзировать одним из следующих способов:

1) с помощью адреса предварительно объявленной простой целочисленной переменной. Например,

int v; ap[i]=&v;

2) с помощью объявленного указателя. Например,

int * p; p= new int; ap[i]=p;

3) непосредственно с помощью операции new. Например,

a) … ap[i]=new int; резервируем память для размещения одного целого числа и его адрес засылаем в i-й элемент массива указателей. Значение i должно быть определено.

б) резервируем память для размещения массива из m целых чисел и адрес его начала засылаем в i-й элемент массива указателей

unsigned m; cin>>m;…ap[i]=new int[m];

В качестве m можно использовать как переменную (см. в примере), так и константу. Переменная i может принимать значение от 0 до n-1;




4) для инициализации элемента массива ap можно использовать адрес предварительно объявленного одномерного массива фиксированной размерности:

const m=10; int a[m]; ap[i]=a;

где i по-прежнему может принимать значение от 0 до n-1 (а не до m-1).

Напомним, что при таком объявлении a — адрес начала одномерного массива. Поэтому последнее присваивание будет корректным.

Логическим продолжением последнего способа является использование массива указателей при работе со статической числовой матрицей. Пусть объявлена матрица

const п=5, m=10; int matr[n][m];

и надо переставить к1-ю строку с k2-й строкой.

Один из способов, при котором элементы двух строк физически переставляются в памяти, был показан в первом семестре. Так как matr[k1] и matr[k2] — адреса начала k1-й и k2-й строк, то возникает вопрос: как для такой перестановки строк ограничиться изменением нескольких адресов, не перемещая элементы в памяти? Объявим для этого ещё один указатель int *p. Казалось бы, можно изменить адреса следующим образом:



p=matr[k1]; matr[k1]=matr[k2]; matr[k2]=p; // ошибка

Но при компиляции второго и третьего присваиваний будут ошибки, так как matr[k1] и matr[k2] — константные указатели на начало строк, то есть их значения нельзя менять. Заметим, что если бы такая возможность разрешалась, то мы потеряли бы доступ к строкам матрицы.

Выход из этой ситуации следующий. Объявляем дополнительно массив указателей размерности n, где n — количество строк матрицы: int * ap[n]; Инициализируем этот массив:

for (int i=0; i<n; i++) ap[i]=matr[i];

Почему присваивание, записанное в цикле, будет корректным? С одной стороны, ap[i] — это указатель, так как ap объявлен не как массив целых чисел (int ap[n];) и не как один указатель (int * ap;) , а как массив указателей. Напомним, что с другой стороны, объявив статическую матрицу, мы этим самым объявили и константый массив указателей, то есть matr[i] — это адрес начала i-й строки матрицы. И тогда меняем не matr[k1] и matr[k2], а ap[k1] и ap[k2] аналогичным образом:

int *p; p=ap[k1]; ap[k1]=ap[k2]; ap[k2]=p;

Вместо m*3 присваиваний, которые необходимо было выполнить при физическом перемещении элементов строк, достаточно выполнить всего три операции присваивания для адресов. При необходимости такие “перестановки” строк можно выполнять несколько раз в цикле. Это имеет место, например, в алгоритмах сортировки. Понятно, что в этом случае эффективность такого метода возрастает.

После таких “перестановок” матрица matr не изменилась. В этом легко убедиться, если вывести её (matr[i][j]) на экран. Доступ к преобразованной матрице осуществляется с помощью идентификатора ap. Например, вывод изменённой матрицы можно выполнить так:

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

{ cout<< endl;

for(int j=0; j<m; j++)

printf ( “%5d”, ap[i][j]);

}

Заметим, что несмотря на то, что, казалось бы, переменную ap не объявляли как двумерный массив (как матрицу), а как одномерный массив, но массив указателей, мы имеем возможность использовать эту переменную с двумя индексами (ap[i][j]).

Пример (+). Сортировка строк целочисленной матрицы по возрастанию некоторого параметра строки (например, по возрастанию максимальных элементов строк). Порядок чисел в каждой строке не меняется.

const m=4;

int main()

{ // 1) Статическую матрицу не создаём. Её достаточно объявить

const n=5; int matr[n][m];

// Cтатический массив указателей на строки матрицы

int*u[n];

// 2) Получаем матрицу…

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

{ for(int j=0;j<m;j++)

matr [i][j]=random(15)-6;

/* … и формируем статический массив указателей на строки матрицы */

u[i]= matr [i];

}

/* 3) Получаем одномерный массив параметров s, каждый элемент которого —максимальное число в строке. Поэтому размерность этого массива совпадает с количеством строк матрицы */

int s[n],Mymax,Mymin,nmin;

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

{ Mymax= matr [i][0];

for(int j=0;j<m;j++)

if (matr [i][j]>Mymax)

Mymax= matr [i][j];

s[i]=Mymax;

}

// 4) Вывод матрицы matr и вектора s

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

{ cout<<endl;

for(int j=0;j<m;j++)

printf("%5d", matr [i][j]);

printf(" => %5d",s[i]);

}

cout<<endl;

// 5) Сортировка методом выбора минимального элемента

for(int start=0;start<=n-2;start++)

{

Mymin=s[start]; nmin=start;

for(int i=start;i<n;i++)

if(s[i]<Mymin)

{ Mymin=s[i];

nmin=i;

}

cout<< Mymin<< " "<<nmin<<endl;

// Комментарии в конце

int *p; p=u[start]; u[start]=u[nmin]; u[nmin]=p;

/* Переставляем элементы одномерного массива s с номерами start и nmin, не используя никакие указатели */

int t; t=s[start]; s[start]=s[nmin]; s[nmin]=t;

}

/* 6) Выводим рассортированную матрицу u (а не matr) и вектор s. */

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

{ cout<<endl;

for(int j=0;j<m;j++)

printf("%5d",u[i][j]);

printf(" => %5d",s[i]);

}

cout<<endl;

/* 7)Убеждаемся, что матрица matr осталась не рассортированной! Матрицы u и matr занимают одну и туже оперативную память, но u[i] и matr [i] имеют, вообще говоря, разные адреса. */

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

{ cout<<endl;

for(int j=0;j<m;j++)

printf("%5d",matr[i][j]);

}

getch(); return 0;

}

В первом семестре без использования указателей две строки матрицы c номерами start и nmin переставляли бы “физически” следующим образом:

int t1;

for(int j=0;j<m;j++)

{ t1= matr [start][j];

matr [start][j]= matr [nmin][j];

matr [nmin][j]=t1; }

Так как matr [i] – это константные адреса строк статической матрицы, их нельзя менять. Т. е. нельзя написать matr [start]= matr [nmin]. Поэтому переставляли полученные “новые” адреса строк матрицы с номерами start и nmin, то есть соответствующие элементы массива указателей u. Физически элементы этих строк остаются на старых местах, меняли только значения двух адресов в массиве u.






Сейчас читают про: