С помощью указателей можно создавать динамические массивы — это массивы, память для которых выделяется не на этапе компиляции, а во время исполнения программы. Это очень удобно. Ведь программист далеко не всегда может заранее представить, какого размера должен быть тот или иной массив. Указывать размер массива с большим запасом (на всякий случай!) не разумно. Проще на этапе исполнения предложить пользователю задать самому необходимое количество элементов массива, либо требуемый размер массива должен вычисляться в программе по каким-то формулам.
Для динамического выделения памяти служит операция new, освобождение ставшей ненужной памяти выполняется операцией delete.
Приведём формальную запись оператора, с помощью которого выделяется память под динамический массив:
Указатель = new Тип [Количество_элементов];
где Тип — это тот же тип данных, что и тип, на который рассчитан указатель.
Запись оператора для освобождения динамической памяти ещё проще:
delete [] Указатель;
Здесь пустые квадратные скобки — это признак того, что освобождается память, отводимая для массива, а не для одиночного объекта.
Пример. Дан массив целых чисел из n элементов. Инвертировать этот массив, т.е. поменять местами первый элемент и последний, второй — и предпоследний и так далее. Память под массив выделять динамически.
Возможный текст программы:
#include <iostream>
using namespace std;
int main()
{
int i, j, n;
int *x;
int c;
// Задаём размер будущего массива
do {
cout << "n=";
cin >> n;
} while(n < 1);
// Динамически выделяем память под массив
x = new int[n];
// Ввод массива с клавиатуры
cout << "Input " << n << " numbers:" << endl;
for(i = 0; i < n; i++)
cin >> x[i];
// Перестановка элементов массива
for(i = 0, j = n - 1; i < j; i++, j--)
{
c = x[i];
x[i] = x[j];
x[j] = c;
}
// Вывод массива на экран монитора
cout << "Otvet:" << endl;
for(i = 0; i < n; i++)
cout << x[i] << endl;
// Освобождение динамической памяти, отводимой под массив
delete [] x;
return 0;
}
Как видно из примера, после того, как память под динамический массив выделена, с ним можно работать точно также, как и с обычным массивом.
Важно не забывать освобождать память, занимаемую динамическими объектами. Иначе могут начаться проблемы в работе компьютера в целом. Неосвобождённые программой участки памяти могут оказаться недоступны после завершения работы вашей программы. Это явление называют «утечка памяти». Конечно, в современных операционных системах всегда реализуется «сборка мусора», т.е. есть некая служебная программа, которая наводит порядок, отыскивает «бесхозные» участки памяти и возвращает их в общее пользование. Но не надо излишне полагаться на «дядю», т.к. любые проблемы операционной системы из-за неудачной оптимизации ОС, из-за вирусов и тому подобного, могут отразиться на работе «сборщика мусора». В этом случае именно ваша программа будет регулярно «подвешивать» систему.
Выделять память динамически можно не только под массивы, но и под одиночные объекты. Конечно, если одиночный объект имеет стандартный тип (double, int и т.д.), то проще использовать обычные переменные. При работе с пользовательскими типами (структуры, классы) в ряде случаев есть смысл в динамическом выделении памяти под одиночные объекты, а для построения динамических структур типа списки, очереди, деревья и т.д. это просто необходимо.
Пусть имеется некий класс (или структура) Alfa (о классах и структурах речь пойдёт позже. Сейчас можно относится к ним просто как к типам данных). Тогда можно сделать следующее:
а)Создаём указатель x на объект типа Alfa:
Alfa *x;
б)Выделяем память под динамический объект:
x = new Alfa;
в)Далее работаем с объектом класса через указатель x. Как это делается — будем разбираться позже, при изучении структур и классов.
г)Как только объект стал ненужным — освобождаем занимаемую им память:
delete x;
В общем, всё делается по аналогии с динамическими массивами, но только ещё проще. Не надо указывать количество элементов при выделении памяти, не нужны квадратные скобки при её освобождении.
Мы разобрались с тем, как динамически выделять память под одиночные объекты и одномерные массивы. Теперь настало время решить эту же проблему и для матриц.
Как мы уже знаем из темы «Матрицы», матрица — это массив из одномерных массивов, каждый из которых представляет одну строку матрицы. Напрашивается идея: завести для каждой строки матрицы указатель и динамически выделить память под заданное количество элементов. Так как строк в матрице может быть достаточно много, а указатели, используемые для выделения памяти под каждую строку матрицы, имеют один и тот же тип, то эти указатели есть смысл объединить в одномерный массив указателей. Естественно, память под массив указателей также будем выделять динамически. А адрес этого массива указателей будем хранить в указателе на указатель. На рисунке покажем, как это может выглядеть:
Здесь
a — это указатель на массив указателей. Формальное описание для него следующее:
Тип ** имя_указателя;
a[i] — указатели на строки матрицы, в которых будут в дальнейшем хранится данные, т.е. элементы матрицы a[i][j].
Порядок работы с динамической матрицей рассмотрим на примере.
Пример. В прямоугольной матрице подсчитать количество отрицательных элементов. Память под матрицу выделять динамически.
Возможный текст программы:
#include <iostream>
using namespace std;
int main()
{
int n = 3; // число строк
int m = 4; // число столбцов
double **a; // указатель на указатель на тип double
int i, j;
// Динамическое выделение памяти под матрицу:
// ------------------------------------------
// 1)создаём массив указателей типа указатель на double
a = new double* [n];
// 2)теперь выделяем память (построчно) собственно под матрицу, т.е. под данные
for(i = 0; i < n; i++)
a[i] = new double[m];
//-------------------------------------------
// Ввод матрицы с клавиатуры
cout << "Matriza A("<< n << "*" << m << "):" << endl;
for(i = 0; i < n; i++)
for(j = 0; j < m; j++)
cin >> a[i][j];
// Подсчёт количества отрицательных чисел в матрице
int k = 0;
for(i = 0; i < n; i++)
for(j = 0; j < m; j++)
if(a[i][j] < 0)
k++;
cout << "k=" << k << endl;
// Освобождение динамической памяти:
// ---------------------------------
// 1)вначале освободим память, отведённую собственно под матрицу, т.е. под данные
for(i = 0; i < n; i++)
delete []a[i];
// 2)теперь освобождаем память, занятую массивом указателей
delete []a;
// ---------------------------------
return 0;
}
Как видим, вначале выделяется память под массив указателей, затем под данные. Освобождение памяти делается в обратном порядке: вначале освобождают память, занятую данными матрицы, а затем — массивом указателей.
Когда память выделена, действия с элементами динамической матрицы выполняются точно так же, как и с обычной матрицей, т.е. везде, где это необходимо, используем обычную форму обращения к элементу матрицы a[i][j].