Распределение динамической памяти

3.1. Операция new

Операция new — один из способов определения значения переменной указателя. Ее синтаксис такой: указатель = new имя_типа; Например, если объявлен указатель float *p; то операция p=new float; выполняет следующее:

1. Выделяет и делает доступным свободный участок динамической памяти, размер которого соответствует типу данных, определяемому именем типа (в примере 4 байта для размещения вещественного числа).

2. Кроме этого, в случае успешного выполнения операция new возвращает адрес начала выделенного участка. В примере значение этого адреса присваивается переменной-указателю (переменной p).

Но значение выделенной ячейки, которое в программе обозначается *p, после выполнения этой операции ещё не определено. Поэтому cout<<(*p) выведет случайное число. Для задания значения *p надо выполнить, например, ввод cin >> (*p); или использовать другой способ (присваивание, получение с помощью функции и т. п.). Обратим внимание, что речь идёт об определении *p, а не p. Поэтому напомним, что cin>>p; или p=1.1; ошибочны.

Если участок нужных размеров не может быть “найден”, то операция new возвращает нулевое (точнее, неопределённое) значение адреса. Оно обозначается в программе константой NULL, которая записывается обязательно большими буквами. Тогда в программе после p=new float; желательно записать:

if (p==NULL) // или if (!p)

{ cout<<”\nError”; getch(); exit(0);}.

Как нулевое числовое значение, так и значение NULL для указателей равносильно false в операциях сравнения.

Стандартная функция exit(0) прекращает выполнение всего проекта. Она более “сильная” по сравнению с оператором return, так как осущетвляет выход не только из функции, где записана, а останавливает работу всего проекта. Заметим, что самым “слабым” в этой группе является оператор break, который прерывает всего лишь цикл или оператор выбора switch, продолжая выполнение функции, если цикл или switch не были последними в ней.

С помощью рассматриваемой операции new можно проинициализировать не только значение указателя, но и задать значение выделенной ячейки памяти. В таком случае операция применяется так: указатель = new имя_типа(значение);. Например, p=new float(3.1); определяет как значение указателя p, так и значение *p. Это равносильно p=new float; *p=3.1; Заметим, что значение записывается в круглых, а не в квадратных скобках. Квадратные скобки будем позже использовать для создания динамического массива. Но динамический массив так инициализировать нельзя! (см. следующую главу). Доступ к переменной, адрес которой находится в p, выполняется операцией “*” (разыменование). Поэтому cout<<p<<” “ <<(*p); выведет шестнадцатеричный адрес и число 3.1.

3.2. Операция delete

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

Для предыдущего примера delete p; освобождает ячейку, адрес которой хранился в переменной-указателе (p). Но при этом ячейка p, в которой находится адрес вещественного числа, из памяти не удаляется и её можно продолжать использовать. Но значение и p, а, значит, и *p, после выполнения операции не определено.

Операцию delete необходимо использовать только для указателя, получившего значение с помощью new. Например, если float x; float *q=&x;, то результат операции delete q; непредсказуем.

Нельзя использовать эту операцию и для статического массива. Следующий код const n=10; int a[n]; … delete a; приводит к ошибке!

Параметры-указатели.

Функция ввода scanf

Мы уже знаем два способа передачи параметров (не массивов) в функцию: передача по значению и по ссылке. Когда полученное или изменённое в функции значение необходимо возвратить в вызывающую функцию, можно использовать третий способ передачи параметра: с помощью указателя.

Для повторения и сравнения приведём все три способа передачи параметров.

Пример 1. /* 1) В первой функции MyMax1 рассматривается передача параметров с помощью указателей. Изменённые значения с их помощью возвращаются из MyMax1 в вызвавшую её функцию.Обратим внимание, что в тексте функции для доступа к числам, находящимся в ячейках с адресами x и у, необходимо использовать операцию разадресации. */

void MyMax1 (int* x, int* y)

{ if (*x>*y) { int t=*x; *x=*y; *y=t; } }

/* 2) Передача параметров с помощью ссылочного типа (повторение). */

void MyMax2 (int &x, int &y)

{ if (x>y) { int t=x; x=y; y=t; } }

/* 3) При передаче параметров по значению (повторение) изменённые значения не возвращаются из функции в вызвавшую её функцию. */

void MyMax3 (int x, int y)

{ if (x>y) { int t=x; x=y; y=t; } }

/* В последних двух вариантах в тексте функции никакие операции над указателями (ни *, ни &) не используются, так как x и y — не указатели.

Вызов функций зависит от того, работаем мы с простыми переменными или с указателями. */

int main()

{ /* Первый вариант вызова функций. Используем не указатели, а “простые” обычные переменные(a и b). */

int a, b; cout<<"Two int numbers"<<endl;

/* Стандартная функция для ввода scanfи “наша” функция MyMax1 в качестве формальных параметров использует указатели. Поэтому в качестве фактических параметров в функцию передаём &a и&b, (адреса переменных a и b.) */

scanf("%d %d", &a, &b); MyMax1(&a, &b);

cout<<"a = "<<a<<"; b = "<<b<<endl;

/* Если ввели, например, 5 и 2, то выведем a = 2; b = 5. Почему? В x передаётся &a — адрес ячейки a. Поэтому a в main и *x в MyMax1 (аналогично b в main и *y в MyMax1) — это одни и те же ячейки памяти, только по-разному называются. */

scanf("%d %d", &a,&b); MyMax2(a,b);

cout<<"a = "<<a<<"; b = "<<b<<endl;

/*Аналогично, если ввели, например, 5 и 2, то выведем a = 2; b = 5, так как a в main и x в MyMax2 (b в main и y в MyMax2) — это одни и те же ячейки памяти, только по-разному называются. */

scanf("%d %d", &a,&b); MyMax3(a,b);

cout<<"a = "<<a<<"; b = "<<b<<endl;

/* Здесь a и b не изменились после выполнения функции, так как a в main и x в MyMax3 (b и y) — это разные ячейки памяти.

Второй вариант вызова функций. Используем указатели, значения которых надо не забыть проинициализировать. */ int* p1=new int; int* p2=new int;

/* Так как функции scanf и MyMax1 требуют, чтобы были переданы адреса, то при их вызове операция разадресации не используется. А поскольку, кроме этого, переменные объявлены как указатели, то при вызове не используем и операцию & (взятие адреса). Переменные p1 и p2 уже являются адресами без этой операции. Поэтому вызов будет таким: */

scanf ("%d %d", p1, p2); MyMax1(p1, p2);

/* Наоборот, при работе с ячейками, адреса которых в p1 и в p2, надо не забыть использовать операцию разадресации. Без неё были бы выведены адреса, а не числа, с которыми мы работаем. */

cout<<endl<< "*p1="<<(*p1) << "; *p2="<<(*p2);

scanf ("%d %d",p1,p2);

/* Так как функции MyMax2 и MyMax3 в качестве параметров адреса не используют, то в качестве фактических параметров должны записать содержимое ячеек p1 и p2, которое получается как результат операции разадресации (*). */

MyMax2(*p1,*p2); cout<<endl<<"*p1="<<(*p1) <<"; *p2="<<(*p2);

scanf ("%d %d",p1,p2); MyMax3 (*p1,*p2);

cout<<endl<<"*p1="<<(*p1) <<"; *p2="<<(*p2);

getch(); return 0; }

Упражнения, тесты

1.Пусть int t=10; int *p=&t; Какие из следующих операторов допустимы и что они означают?

1) cin>>p; 2) cin>>(*p); 3) cout<< p; 4) cout<<(*p); 5) p=1000; 6)*p=1000; 7) t*=*p; 8) t*=p; 9) t=*p; 10) t=p; 11) cout <<(*p*=2)?

2. Дан код:

void fun2 (int *x, int &y) { (*x)++; y--; } //1

void main() { int a=5, b=2; fun2 (&a, b); //2

cout<< a<< “ “<< b; getch() ; }

Что будет выведено?

Варианты ответов: 1) 6 1; 2) 5 1; 3) 6 2; 4) 5 2; 5) Ошибка в //1; 6) Ошибка в //2; 7) Ошибки в //1 и //2.

3. Дан код:

void fun3 (int x, int *y) { *y=x+10; } //1

void main() { int a=5, b=2; int* p=&b; /*2 */

fun3 (a,*p); /*3*/

cout<< a<< “ “<< b; getch() ; }

Что будет выведено?

Варианты ответов: 1) 5 15 ; 2) 15 2; 3) 5 2; 4) Ошибка в //1; 5) Ошибка в /*2*/; 6) Ошибки в /*3*/ .

4. Дан код:

void fun4 (int x, int *y) { * y=x*10; } //1

void main() { int a=5, *b=new int (2); //2

fun4 (????????); //3

cout<< a<< “ “<< (*b); getch() ; } //4

Как вызвать функцию в //3, чтобы вывести 5 50?

Варианты ответов:

1) fun3(a,b); 2) fun3(a, &b); 3) fun3(&a, b); 4) fun3(&a, &b); 5) fun3(a, *b); 6) fun3(*a, b); 7) fun3(*a, *b); 8) ошибки (указать номера строк).

5.Дан код:

void fun5(int &x, int *y) { ????????????? ; } //1

void main() { int a=5, *b=new int; //2

fun5 (a,b); //3

cout<<endl<<a<<" "<<(*b)<<endl; //4

getch(); }

Что записать в тексте функции в строке //1, чтобы вывести 5 15?

Варианты ответов:

1) y=x+10; 2) *y=*x+10; 3) *y=x+10; 4) y=*x+10;

5)ошибка в //2; 6) ошибка в //3; 7) ошибка в //4.

6. Дан код:

int *p1= new int = 11; int *p2; *p2= new int =11;

int *p3= new int[11]; int *p4= new int (11);

int *p5= new (int) 11; int *p6= new int :11;

Для каких указателей (p1 — p6) правильно резервируется память для одной целочисленной переменной и выполняется её инициализация?

Г л а в а 8
одномерные МАССИВЫ, указатели и функции

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

Наши рекомендации