Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lesson_12_13.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
365.57 Кб
Скачать
  1. Выделение памяти с помощью операции new

До сих пор мы инициализировали указатели адресами переменных; переменные — это именованная память, выделенная во время компиляции, и каждый указатель, до сих пор использованный в примерах, просто представлял собой псевдоним для памяти, доступ к которой и так был возможен по именам переменных. Реальная ценность указателей проявляется тогда, когда во время выполнения выделяются неименованные области памяти для хранения значений. В этом случае указатели становятся единственным способом доступа к такой памяти.

Лучший способ — операция new.

Вы сообщаете new, для какого типа данных запрашивается память; new находит блок памяти нужного размера и возвращает его адрес. Вы присваиваете этот адрес указателю, и на этом все. Ниже показан пример:

int * pn = new int;

Часть new int сообщает программе, что требуется некоторое новое хранилище, подходящее для хранения int. Операция new использует тип для того, чтобы определить, сколько байт необходимо выделить. Затем она находит память и возвращает адрес. Далее вы присваиваете адрес переменной рп, которая объявлена как указатель на int. Теперь рп — адрес, а *рп — значение, хранящееся по этому адресу. Сравните это с присваиванием адреса переменной указателю:

int higgens;

int * pt = &higgens;

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

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

имяТипа * имя_указателя = new имяТипа;

Тип данных используется дважды: один раз для указания разновидности запрашиваемой памяти, а второй — для объявления подходящего указателя.

Использование операции new для разных типов: use_new.cpp

#include <iostream>

using namespace std;

void main()

{

setlocale (LC_CTYPE, "russian");

int a=100;

int *ptr=new int; //выделение пространства для int

*ptr=100; //сохранение в нем значения

double *pdr=new double;

*pdr=1000.01;

cout << "int a= " << a << ": адрес = " << &a << endl << endl;

cout << "int *ptr= " << *ptr << ": адрес int значения = " << ptr << endl;

cout << "адрес указателя ptr = " << &ptr << endl << endl;

cout << "double *pdr= " << *pdr << ": адрес double значения = " << pdr << endl;

cout << "адрес указателя pdr = " << &pdr << endl << endl;

cout << "размер ptr= " << sizeof (ptr) << ":размер *ptr= " << sizeof (*ptr) << endl;

cout << "размер pdr= " << sizeof (pdr) << ":размер *pdr= " << sizeof (*pdr) << endl;

cin.get(); cin.get();

}

*ptr – значение

ptr – сам указатель, таким образом показывается адрес значения

&ptr – адрес указателя

Переменные a, ptr, pdr хранят свои значения в области памяти под названием стек, тогда как память, выделяемая операцией new, находится в области, называемой кучей или свободным хранилищем.

Освобождение памяти с помощью операции delete

Использование операции new для запрашивания памяти, когда она нужна — одна из сторон пакета управления памятью C++. Второй стороной является операция delete, которая позволяет вернуть память в пул свободной памяти, когда работа с ней завершена. Это — важный шаг к максимально эффективному использованию памяти. Память, которую вы возвращаете, или освобождаете, затем может быть повторно использована другими частями программы. Операция delete применяется с указателем на блок памяти, который был выделен операцией new:

int * ps = new int; // выделить память с помощью операции new

... // использовать память

delete ps; // по завершении освободить память с помощью операции delete

Это освобождает память, на которую указывает ps, но не удаляет сам указатель ps. Вы можете повторно использовать ps — например, чтобы указать на другой выделенный new блок памяти. Вы всегда должны обеспечивать сбалансированное применение new и delete; в противном случае вы рискуете столкнуться с таким явлением, как утечка памяти, т.е. ситуацией, когда память выделена, но более не может быть использована. Если утечки памяти слишком велики, то попытка программы выделить очередной блок может привести к ее аварийному завершению.

Вы не должны пытаться освобождать блок памяти, который уже был однажды освобожден!

Нельзя с помощью операции delete освобождать память, которая была выделена посредством объявления обычных переменных:

int * ps = new int; // нормально

delete ps; // нормально

delete ps; // теперь- не нормально!

int jugs = 5; // нормально

int * pi = &jugs; // нормально

delete pi; // не допускается, память не была выделена new

Внимание!

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

Использование операции new для создания динамических массивов

Если вы создаете массив простым объявлением, пространство для него распределяется раз и навсегда — во время компиляции. Будет ли востребованным массив в программе или нет — он все равно существует и занимает место в памяти. Распределение массива во время компиляции называется статическим связыванием и означает, что массив встраивается в программу во время компиляции. Но

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

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

Создание динамического массива с помощью операции new

Синтаксис предусматривает указание имени типа с количеством элементов в квадратных скобках. Например, если необходим массив из 10 элементов int, следует записать так:

int * psome = new int [10] ; // получение блока памяти из 10 элементов типа int

Операция new возвращает адрес первого элемента в блоке. В данном примере это значение присваивается указателю psome.

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

delete [] psome; // освобождение динамического массива

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

При использовании new и delete необходимо придерживаться перечисленных ниже правил.

• Не использовать delete для освобождения той памяти, которая не была выделена new.

• Не использовать delete для освобождения одного и того же блока памяти дважды.

• Использовать delete [], если применялась операция new[] для размещениямассива.

• Использовать delete без скобок, если применялась операция new для размещения отдельного элемента.

• Помнить о том, что применение delete к нулевому указателю является безопасным (при этом ничего не происходит).

psome — это указатель на отдельное значение int, являющееся первым элементом блока.

Общая форма выделения и назначения памяти для массива выглядит следующим образом:

имя_типа имя_указателя = new имя_типа [количество_элементов] ;

Использование динамического массива

Следующий оператор создает указатель psome, который указывает на первый элемент блока из 10 значений int:

int * psome = new int [10]; // получить блок для 10 элементов типа int

Для доступа к элементам массива следует использовать указатель, как если бы он был именем массива. То есть можно писать psome [0] вместо *psome для первого элемента, psome [1] — для второго и т.д.

Использование операции new для массивов

Pointer_new_massiv.cpp

#include <iostream>

using namespace std;

void main()

{

setlocale (LC_CTYPE, "russian");

double *ptr = new double [3];

ptr[0]=0.1;

ptr[1]=0.5;

ptr[2]=0.8;

cout << "ptr[1] = " << ptr[1] << endl << endl;

ptr += 1;

cout << "Now ptr[0] = " << ptr[0] << endl;

cout << "ptr[1] = " << ptr[1] << endl;

ptr -= 1;

delete []ptr;

cin.get(); cin.get();

}

Фундаментальное отличие между именем массива и указателем проявляется в следующей строке:

ptr = ptr + 1; // допускается для указателей, но не для имен массивов

На заметку!

Добавление единицы к переменной указателя увеличивает его значение на количество байт, представляющее размер типа, на который он указывает.(double – 8, short -2 байта и тд.)

Вы не можете изменить значение для имени массива. Но указатель — переменная, а потому ее значение можно изменить. Обратите внимание на эффект от добавления 1 к ptr. Теперь выражение ptr [0] ссылается на бывший второй элемент массива. То есть добавление 1 к ptr заставляет ptr указывать на второй элемент вместо первого. Вычитание 1 из значения указателя возвращает его назад, в исходное значение, поэтому программа может применить delete [ ] с корректным адресом.

Операция выделения памяти new

С помощью вышеозначенной операции мы можем себе позволить выделять память динамически - т. е. на этапе выполнения программы.

Часто выражение, содержащее операцию new, имеет следующий вид:

указатель_на_тип_= new имя_типа (инициализатор)

Инициализатор - это необязательное инициализирующее выражение, которое может использоваться для всех типов, кроме массивов.

При выполнении оператора

int *ip = new int;

создаются 2 объекта: динамический безымянный объект и указатель на него с именем ip, значением которого является адрес динамического объекта. Можно создать и другой указатель на тот же динамический объект:

int *other=ip;

Если указателю ip присвоить другое значение, то можно потерять доступ к динамическому объекту:

int *ip=new (int);

int i=0;

ip=&i;

В результате динамический объект по-прежнему будет существовать, но обратиться к нему уже нельзя. Такие объекты называются мусором.

При выделении памяти объект можно инициализировать:

int *ip = new int(3);

Можно динамически распределить память и под массив:

double *mas = new double [50];

Далее с этой динамически выделенной памятью можно работать как с обычным массивом:

#include <iostream>

#include <stdlib.h>

#include <time.h>

using namespace std;

void main(){

srand(time(NULL));

int size;

int * dar;

// запрос размера массива с клавиатуры

cout<<"Enter size:\n";

cin>>size;

//выделение памяти под массив с количеством элементов size

dar=new int [size];

if(!dar){

cout<<"Sorry, error!!!";

exit(0);// функция организует выход из программы

}

// заполнение массива и показ на экран

for(int i=0;i<size;i++){

dar[i]=rand()%100;

cout<<dar[i]<<"\t";

}

cout<<"\n\n";

}

В случае успешного завершения операция new возвращает указатель со значением, отличным от нуля.

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

Операция освобождения памяти delete

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

delete ip; // Удаляет динамический объект типа int,

// если было ip = new int;

delete [ ] mas; // удаляет динамический массив длиной 50, если было

// double *mas = new double[50];

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

Чтобы избежать подобных ошибок, можно применять следующую конструкцию:

int *ip=new int[500];

. . .

if (ip){

delete ip; ip=NULL;

}

else

{

cout <<" память уже освобождена \n";

}

В наш, вышеописанный пример, мы теперь можем добавить освобождение памяти.

#include <iostream>

#include <stdlib.h>

#include <time.h>

using namespace std;

void main(){

srand(time(NULL));

int size;

int * dar;

// запрос размера массива с клавиатуры

cout<<"Enter size:\n";

cin>>size;

//выделение памяти под массив с количеством элементов size

dar=new int [size];

if(!dar){

cout<<"Sorry, error!!!";

exit(0);// функция организует выход из программы

}

// заполнение массива и показ на экран

for(int i=0;i<size;i++){

dar[i]=rand()%100;

cout<<dar[i]<<"\t";

}

cout<<"\n\n";

// освобождение памяти

delete[]dar;

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]