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

4.4.3. Динамические массивы.

При решении на компьютере серьезных задач важно экономно расходовать имеющуюся память и освобождать ее по мере возможности. Прин­цип организации динамического двухмерного массива очень похож на принцип организации статическо­го двухмерного массива. Отличие состоит в том, что теперь для адресов а, а[0], а[1],..., а[n-1] должно быть отведено реальное физическое пространство памя­ти, в то время как для статического двухмерного массива выражения вида а, а[0], а[1],..., а[n-1] были лишь возможными конструкциями для ссылок на ре­ально существующие элементы массива, но сами эти указатели не существовали как объекты в памяти компьютера.

Алгоритм выделения памяти таков:

  • Определяем переменную а как адрес массива адресов: float **a;.

  • Захватываем память из области heap для массива из n указателей на тип float и присваиваем адрес начала этой памяти указателю а. Оператор, вы­полняющий это действие, выглядит так: а = new float* [n];.

  • В цикле пробегаем по массиву адресов а[], присваивая каждому указателю а[i] адрес вновь захватываемой памяти под массив из n чисел типа float.

При работе с динамически задаваемыми массивами начинающие часто забы­вают освобождать память, захваченную для массива. Память следует вновь воз­вращать в распоряжение операционной системы, то есть освобождать операцией delete. Правда, при завершении работы функции main автоматически уничтожают­ся все переменные, созданные в программе, и указатели сегментов памяти получа­ют свои исходные значения. Однако при разработке сложных многомодульных комплексов программ следует помнить о том, что захваченная память повисает, становится недоступной операционной системе при выходе из области действия указателя, который ссылается на ее начало. Это может вызвать отказ в выделе­нии новой памяти в каком-то другом программном модуле, если весь объем облас­ти heap будет исчерпан. Операция delete совместно с операцией new позволяет контролировать процесс последовательного захвата и высвобождения динами­ческой памяти. Чтобы освободить память, захваченную для одной переменной d, например с помощью оператора

double *d=new double;

достаточно в конце функ­ции или блока, где использовалась переменная d, записать:

delete d;

Если был размещен массив переменных, например

float *p = new float[200],

то следует освобождать память оператором delete [] р;. Здесь квадратные скобки указывают компилятору на то, что освобождать следу­ет то количество ячеек, которое было захвачено последней операцией new в при­менении к указателю р. Явно указывать это число не нужно.

4.4.4. Массивы указателей.

Указатели делятся на две основные категории: указатели на переменные и указа­тели на функции. Обе категории содержат адреса памяти, но они имеют различные свойства и назначение. Для них также справедливы и различ­ные правила использования. Указатели на функцию используются с целью вызова различных функций, удовлетворяющих определенному прототипу. Выполнение каких-то арифметических операций над указателем на функцию лишено смысла и не допускается в языке C++. Указатели на переменные, обычно на массивы, допускают произведение арифметических действий над собой с целью, напри­мер, пробега по массиву. Хотя указатели и содержат числа со свойствами целых без знака (unsigned int), тем не менее, для них существуют свои собственные правила и ограничения при выполнении операций присвоения, преобразования и адресной арифметики. Следующий пример демонстрирует использование ука­зателя на функции. Объявление double (*pFunc)(double); определяет перемен­ную pFunc, имеющую тип указателя на функции. Эта переменная способна ука­зывать на любую функцию, которая возвращает вещественное число и требует одно вещественное число в качестве параметра. Обрамляющие скобки обязатель­ны, так как без них выражение задает прототип функции, которая возвращает указатель на double и требует параметр типа double.

#include <stdio.h>

#include <conio.h>

#include <math.h>

void main()

{ // Использование указателя на функции doublе (*pFunc) (double); // Указатель unsigned c=l;

double y; while (с)

{ // Цикл проверки printf ("\n\tSelect a function (0 - to quit)\n" "\n 0. Quit" "\n 1. Sqrt" "\n 2. Sin" "\n 3. Tan\n\n\t"); с = getch () -' 0'; // Реакция пользователя

switch(c)

{

case 1: pFunc = sqrt; break;

case 2: pFunc = sin; break;

case 3: pFunc = tan; break;

case 0: break;

default: с =0;

}

if (c) {

у = pFunc(1); //Вызов с помощью указателя

printf ("\n y(1) = %f",y);

}

}

}

Обратите внимание на выражение getch()-'0', использованное для анализа символа, введенного пользователем. Вычитая код символа '0' (равный 48), мы преобразуем код введенного символа в целое число, равное расстоянию символа в таблице кодов от символа '0'. Таким образом, если была введена цифра, то выражение дает число, равное этой цифре. Если введена не цифра, то перемен­ная unsigned с содержит некоторое недопустимое число, которое игнорируется, и цикл запроса ввода повторяется.

Тема 4.5. Разработка списковых структур данных в Visual C++.