Распределение динамической памяти
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
одномерные МАССИВЫ, указатели и функции
В этой главе показано, как передать одномерный массив в функцию с помощью указателей, изучаются алгоритмы сортировки одномерных массивов, рассматривается порядок создания и работы с динамическим одномерным массивом, начинаем изучать строки как обычный статический массив символов.