Динамическое размещение массивов


При динамическом распределении памяти для массивов следует описать соответствующий указатель и присваивать ему значение при помощи функции calloc. Одномерный массив a[10] из элементов типа float можно создать следующим образом

float *a; a=(float*)(calloc(10,sizeof(float));

Для создания двумерного массива вначале нужно распределить память для массива указателей на одномерные массивы, а затем распределять память для одномерных массивов. Пусть, например, требуется создать массив a[n][m], это можно сделать при помощи следующего фрагмента программы:

#include main () { double **a; int n,m,i; scanf("%d %d",&n,&m); a=(double **)calloc(m,sizeof(double *)); for (i=0; i<=m; i++) a[i]=(double *)calloc(n,sizeof(double)); . . . . . . . . . . . . }

Аналогичным образом можно распределить память и для трехмерного массива размером n,m,l. Следует только помнить, что ненужную для дальнейшего выполнения программы память следует освобождать при помощи функции free.

#include main () { long ***a; int n,m,l,i,j; scanf("%d %d %d",&n,&m,&l); /* -------- распределение памяти -------- */ a=(long ***)calloc(m,sizeof(long **)); for (i=0; i<=m; i++) { a[i]=(long **)calloc(n,sizeof(long *)); for (j=0; i<=l; j++) a[i][j]=(long *)calloc(l,sizeof(long)); } . . . . . . . . . . . . /* --------- освобождение памяти ----------*/ for (i=0; i<=m; i++) { for (j=0; j<=l; j++) free (a[i][j]); free (a[i]); } free (a); }

Рассмотрим еще один интересный пример, в котором память для массивов распределяется в вызываемой функции, а используется в вызывающей. В таком случае в вызываемую функцию требуется передавать указатели, которым будут присвоены адреса выделяемой для массивов памяти.

Пример: #include main() { int vvod(double ***, long **); double **a; /* указатель для массива a[n][m] */ long *b; /* указатель для массива b[n] */ vvod (&a,&b); .. /* в функцию vvod передаются адреса указателей, */ .. /* а не их значения */ .. } int vvod(double ***a, long **b) { int n,m,i,j; scanf (" %d %d ",&n,&m); *a=(double **)calloc(n,sizeof(double *)); *b=(long *)calloc(n,sizeof(long)); for (i=0; i<=n; i++) *a[i]=(double *)calloc(m,sizeof(double)); ..... }

Отметим также то обстоятельство, что указатель на массив не обязательно должен показывать на начальный элемент некоторого массива. Он может быть сдвинут так, что начальный элемент будет иметь индекс отличный от нуля, причем он может быть как положительным так и отрицательным.

Пример:

#include int main() { float *q, **b; int i, j, k, n, m; scanf("%d %d",&n,&m); q=(float *)calloc(m,sizeof(float)); /* сейчас указатель q показывает на начало массива */ q[0]=22.3; q-=5; /* теперь начальный элемент массива имеет индекс 5, */ /* а конечный элемент индекс n-5 */ q[5]=1.5; /* сдвиг индекса не приводит к перераспределению */ /* массива в памяти и изменится начальный элемент */ q[6]=2.5; /* - это второй элемент */ q[7]=3.5; /* - это третий элемент */ q+=5; /* теперь начальный элемент вновь имеет индекс 0, */ /* а значения элементов q[0], q[1], q[2] равны */ /* соответственно 1.5, 2.5, 3.5 */ q+=2; /* теперь начальный элемент имеет индекс -2, */ /* следующий -1, затем 0 и т.д. по порядку */ q[-2]=8.2; q[-1]=4.5; q-=2; /* возвращаем начальную индексацию, три первых */ /* элемента массива q[0], q[1], q[2], имеют */ /* значения 8.2, 4.5, 3.5 */ q--; /* вновь изменим индексацию . */ /* Для освобождения области памяти в которой размещен */ /* массив q используется функция free(q), но поскольку */ /* значение указателя q смещено, то выполнение */ /* функции free(q) приведет к непредсказуемым последствиям. */ /* Для правильного выполнения этой функции */ /* указатель q должен быть возвращен в первоначальное */ /* положение */ free(++q); /* Рассмотрим возможность изменения индексации и */ /* освобождения памяти для двумерного массива */ b=(float **)calloc(m,sizeof(float *)); for (i=0; i < m; i++) b[i]=(float *)calloc(n,sizeof(float)); /* После распределения памяти начальным элементом */ /* массива будет элемент b[0][0] */ /* Выполним сдвиг индексов так, чтобы начальным */ /* элементом стал элемент b[1][1] */ for (i=0; i < m ; i++) --b[i]; b--; /* Теперь присвоим каждому элементу массива сумму его */ /* индексов */ for (i=1; i<=m; i++) for (j=1; j<=n; j++) b[i][j]=(float)(i+j); /* Обратите внимание на начальные значения счетчиков */ /* циклов i и j, он начинаются с 1 а не с 0 */ /* Возвратимся к прежней индексации */ for (i=1; i<=m; i++) ++b[i]; b++; /* Выполним освобождение памяти */ for (i=0; i < m; i++) free(b[i]); free(b); ... ... return 0; }

В качестве последнего примера рассмотрим динамическое распределение памяти для массива указателей на функции, имеющие один входной параметр типа double и возвращающие значение типа double.

Пример:

#include #include double cos(double); double sin(double); double tan(double); int main() { double (*(*masfun))(double); double x=0.5, y; int i; masfun=(double(*(*))(double)) calloc(3,sizeof(double(*(*))(double))); masfun[0]=cos; masfun[1]=sin; masfun[2]=tan; for (i=0; i

7.6. Динамическое выделение памяти средствами C++

Операторы языка C++ new и delete используются для выделения и освобождения блоков памяти. Область памяти, в которой размещаются эти блоки, известна как свободная память. Оператор new описывает типы данных. Затем выделяется блок памяти достаточной длины для размещения объекта описанного типа, и адрес блока возвращается как указатель на заданный тип данных. Блок памяти можно выделить для хранения объекта встроенного типа, например char, int или double, например:

char *PChar; //объявление указателя int *PInt; double *PDouble;PChar = new char; //размещение объекта в памяти Pint = new int; PDouble = new double;*PChar = 'а'; //присваивание значения*PInt = 5;*PDouble = 2.25;

Оператор new чаще всего используется для размещения в памяти данных определенных пользователем типов, например, структур:

struct Node {char *Name; int Value; Node *Next; }; // ...Node *PNode; // объявить указательPNode = new Node; // разместить в памятиPNode->Name = "hello"; // присвоить значение PNode->Value = 1; PNode->Next = 0;

Если выделить требуемый объем памяти окажется невозможным, то будет возвращено значение 0. Следовательно, необходимо проверять указатель перед его использованием.

PNode = new Node; if (PNode == 0)// условие обработки ошибки ... else// использование PNode ...

Если блок памяти, выделенный оператором new, больше не используется, то его можно освободить с помощью оператора delete с указателем, содержащим адрес блока. Например, следующие операторы освобождают блоки памяти, выде­ленные в предыдущем примере:

delete PChar; delete Pint; delete PDouble; delete PNode;

Совет

Убедитесь, не вызывается ли оператор delete более одного раза с использованием одного адреса. Впрочем, удаление указателя, содержащего 0, всегда безобидно. Поэтому хорошим стилем программирования является установка указателя переменной в 0 сразу после использования оператора delete.

Операторы new и delete полезны для динамического создания объектов в памяти, особенно когда число или размер объектов неизвестны при компиляции. Кроме того, в отличие от глобального или локального объекта, можно точно управлять периодом жизни объекта, созданного оператором new. Глобально определенный объект остается в памяти на протяжении всего времени исполнения программы. Локально определенный объект существует, пока управление программы остается в блоке, в котором он определен. Объект же, созданный оператором new, может быть помещен в любую точку программы и освобожден в любой точке программы с использованием оператора delete.

Операторы new и delete обычно удобней традиционных семейств функций распределения памяти malloc, предоставляемых библиотекой времени выполнения. В отличие от malloc, операция new автоматически определяет правильный размер объекта и возвращает указатель корректного типа. Как вы увидите в следующей главе, при использовании оператора new для объектов типа класс, автоматически вызывается конструктор класса (т.е. функция, инициализирующая его). При использовании оператора delete автоматически вызывается деструктор класса (если он был определен).

Примечание

Операторы new и delete можно перегрузить, чтобы настроить управление памятью в программе. Общие вопросы перегрузки операторов рассмотрены в следующих главах.