Й, 15-16-й, 21-й и 25-й строк.
Рис. 4.. Состояние про-
Граммы 1.2 после выпол-
Нения 7-й строки с описа-
Ниями переменных.
Рис. 5.. Программа 1.2
После выполнения опера-
Торов присваивания в 9-й,
Й и 11-й строках.
Рис. 6.. Программа 1.2
После выполнения опера-
Торов присваивания в 15-
Й и 16-й строках.
78
Рис. 7.. Программа 1.2 после
Выполнения оператора присваи-
Вания в 21-й строке.
Рис. 8.. Программа 1.2 после вы-
полнения оператора "delete" в
Й строке.
Состояния на рис. 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]!= '\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!= '\0')
{
if (LowerCase(*phrase))
ChangeToUpperCase(*phrase);
phrase++;
}
}
Фрагмент программы 2.2.
Эта модификация функции не влияет на остальные части программы – вызовы
Вариантов функций с параметром-указателем или параметром-массивом записывают-
ся одинаково, например:
81
char a_string[] = "Hello World";
...
...
ChangeToUpperCase(a_string);
Динамические массивы
Правила создания и уничтожения динамических переменных типов "int",
"char", "double" и т.п. (см. п.1.3) распространяются и на динамические массивы. По
Отношению к массивам динамическое распределение памяти особенно полезно, по-