Смекни!
smekni.com

Основы программирования на языке Си (стр. 18 из 27)

появляется еще одно имя "*ptr_a". Часто в программах бывает удобно пользоваться

переменными, у которых есть только такие имена динамическими переменными. Не-

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

через указатели с помощью операции разыменования (например, "*ptr_a" и "*ptr_b").

Динамические переменные "создаются" с помощью оператора распределения

динамической памяти "new", а "уничтожаются" (т.е. занимаемая ими память освобож-

77

дается для дальнейшего использования) с помощью оператора "delete". Действие

этих операторов показано в программе 1.2 (она очень похожа на программу 1.1).

#include <iostream.h>

typedef int* IntPtrType;

int main()

{

IntPtrType ptr_a, ptr_b; /* СТРОКА 7 */

ptr_a = new int; /* СТРОКА 9 */

*ptr_a = 4; /* СТРОКА 10 */

ptr_b = ptr_a; /* СТРОКА 11 */

cout << *ptr_a << " " << *ptr_b << "&bsol;n";

ptr_b = new int; /* СТРОКА 15 */

*ptr_b = 7; /* СТРОКА 16 */

cout << *ptr_a << " " << *ptr_b << "&bsol;n";

delete ptr_a;

ptr_a = ptr_b; /* СТРОКА 21 */

cout << *ptr_a << " " << *ptr_b << "&bsol;n";

delete ptr_a; /* СТРОКА 25 */

return 0;

}

Программа 1.2.

Программа 1.2 печатает на экране следующие сообщения:

4 4

4 7

7 7

На рисунках 4-8 показаны состояния программы 1.2 после выполнения 7-й, 9-

11-й, 15-16-й, 21-й и 25-й строк.

Рис. 4.. Состояние про-

граммы 1.2 после выпол-

нения 7-й строки с описа-

ниями переменных.

Рис. 5.. Программа 1.2

после выполнения опера-

торов присваивания в 9-й,

10-й и 11-й строках.

Рис. 6.. Программа 1.2

после выполнения опера-

торов присваивания в 15-

й и 16-й строках.

78

Рис. 7.. Программа 1.2 после

выполнения оператора присваи-

вания в 21-й строке.

Рис. 8.. Программа 1.2 после вы-

полнения оператора "delete" в

25-й строке.

Состояния на рис. 4 и рис. 8 похожи тем, что значения указателей "ptr_a" и

"ptr_b" не определены, т.е. они указывают на несуществующие объекты. Обратите

внимание, что указатель "ptr_b" в конце программы оказывается в неопределенном

состоянии, хотя при вызове оператора "delete" этот указатель явно не передавался.

Если указатель "ptr" указывает на несуществующий объект, то использование

в выражениях значения "*ptr" может привести в непредсказуемым (и часто катастро-

фическим) результатам. К сожалению, в Си++ нет встроенного механизма проверки

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

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

нулевой адрес (символическое имя нулевого адреса "NULL"). Для хранения перемен-

ных в Си++ нулевой адрес не используется.

Константа "NULL" (целое число 0) описана в библиотечном заголовочном файле

"stddef.h". Значение "NULL" можно присвоить любому указателю. Например, в про-

грамме 1.3, являющемся усовершенствованным вариантом программы 1.2, для защи-

ты от использования неопределенных указателей "*ptr_a" и "*ptr_b" были добавлены

следующие строки:

#include <iostream.h>

#include <stddef.h>

...

...

delete ptr_a;

ptr_a = NULL;

delete ptr_b;

ptr_b = NULL;

...

...

if ( ptr_a != NULL )

{

*ptr_a = ...

...

...

Фрагмент программы 1.3.

В случае, если для создания динамической переменной не хватает свободной

оперативной памяти, то после вызова "new" Си++ автоматически присвоит соответст-

вующему указателю значение "NULL". В показанном ниже фрагменте программы 1.4

этот факт используется для организации типичной проверки успешного создания ди-

намической переменной.

#include <iostream.h>

#include <stdlib.h> /* "exit()" описана в файле stdlib.h */

#include <stddef.h>

...

...

79

ptr_a = new int;

if ( ptr_a == NULL )

{

cout << "Извините, недостаточно оперативной памяти";

exit(1);

}

...

...

Фрагмент программы 1.4.

Указатели можно передавать в качестве параметров функций. В программе 1.5

проверка на корректность указателя выполняется в отдельной функции, выполняю-

щей создание динамической целочисленной переменной:

void assign_new_int( IntPtrType& ptr )

{

ptr = new int;

if ( ptr == NULL )

{

cout << "Извините, недостаточно оперативной памяти";

exit(1);

}

}

Фрагмент программы 1.5.

2. Переменные типа "массив". Арифметические операции с указателями

В 6-й лекции были рассмотрены массивы наборы однотипных переменных. В

Си++ понятия массива и указателя тесно связаны. Рассмотрим оператор описания:

int hours[6];

Этот массив состоит из 6-ти элементов:

hours[0] hours[1] hours[2] hours[3] hours[4] hours[5]

Массивы в Си++ реализованы так, как будто имя массива (например, "hours")

является указателем. Поэтому, если добавить в программу объявление целочисленно-

го указателя:

int* ptr;

то ему можно присвоить адрес массива (т.е. адрес первого элемента массива):

ptr = hours;

После выполнения этого оператора обе переменные "ptr" и "hours" будут

указывать на целочисленную переменную, доступную в программе как "hours[0]".

Фактически, имена "hours[0]", "*hours" и "*ptr" являются тремя различными

именами одной и той же переменной. У переменных "hours[1]", "hours[2]" и т.д.

также появляются новые имена:

*(hours + 1) *(hours + 2) ...

или

*(ptr + 1) *(ptr + 2) ...

В данном случае "+2" означает "добавить к адресу указателя смещение, соот-

ветствующее 2-м целым значениям". Из арифметических операций к указателям часто

применяется сложение и вычитание (в том числе операции инкремента и декремента

80

"++" и "--"), а умножение и деление не используются. Значения однотипных указате-

лей можно вычитать друг из друга.

Главное, что нужно запомнить относительно сложения и вычитания значений

из указателя в выражениях Си++ указывается не число, которое нужно вычесть (или

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

"сместить" адрес.

Арифметические выражения с указателями иногда позволяют более кратко за-

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

английской строки в верхний регистр (фрагмент программы 2.1).

void ChangeToUpperCase( char phrase[] )

{

int index = 0;

while ( phrase[index] != '&bsol;0' )

{

if ( LowerCase(phrase[index]) )

ChangeToUpperCase( phrase[index] );

index++;

}

}

bool LowerCase( char character )

{

return ( character >= 'a' && character <= 'z');

}

void ChangeToUpperCase( char& character )

{

character += 'A' - 'a';

}

Фрагмент программы 2.1.

Обратите внимание на полиморфизм функции "ChangeToUpperCase(...)" при

обработке вызова компилятор различает две перегруженных функции, т.к. у них раз-

ные параметры (у одной параметр типа "char", а у другой параметр типа "сим-

вольный массив"). Имя массива "phrase" является переменной-указателем, поэтому

функцию с параметром-массивом можно переписать короче, если использовать

арифметические выражения с указателями:

void ChangeToUpperCase( char* phrase )

{

while ( *phrase != '&bsol;0' )

{

if ( LowerCase(*phrase) )

ChangeToUpperCase(*phrase);

phrase++;

}

}

Фрагмент программы 2.2.

Эта модификация функции не влияет на остальные части программы вызовы

вариантов функций с параметром-указателем или параметром-массивом записывают-

ся одинаково, например:

81

char a_string[] = "Hello World";