Шрифт:
Интервал:
Закладка:
![](images/_001.png)
Предотвратить выход за пределы допустимого диапазона сложно по многим причинам. Одна из них заключается в том, что мы можем присваивать один указатель double* другому указателю double* независимо от количества элементов, на которые они ссылаются. Указатель действительно не знает, на сколько элементов он ссылается. Рассмотрим пример.
double* p = new double; // разместить переменную типа double
double* q = new double[1000]; // разместить тысячи переменных double
q[700] = 7.7; // отлично
q = p; // пусть указатель q ссылается на то же, что и p
double d = q[700]; // выход за пределы допустимого диапазона!
Здесь всего три строки кода, в которых выражение q[700] ссылается на две разные ячейки памяти, причем во втором случае происходит опасный выход за пределы допустимого диапазона.
![](images/_192.png)
Теперь мы надеемся, что вы спросите: “А почему указатель не может помнить размер памяти?” Очевидно, что можно было бы разработать указатель, который помнил бы, на какое количество элементов он ссылается, — в классе vector это сделано почти так. А если вы прочитаете книги, посвященные языку С++, и просмотрите его библиотеки, то обнаружите множество “интеллектуальных указателей”, компенсирующих этот недостаток встроенных низкоуровневых указателей. Однако в некоторых ситуациях нам нужен низкоуровневый доступ и понимание механизма адресации объектов, а машина не знает, что она адресует. Кроме того, знание механизма работы указателей важно для понимания огромного количества уже написанных программ.
17.4.4. Инициализация
Как всегда, мы хотели бы, чтобы объект уже имел какое-то значение, прежде чем мы приступим к его использованию; иначе говоря, мы хотели бы, чтобы указатели и объекты, на которые они ссылаются, были инициализированы. Рассмотрим пример.
double* p0; // объявление без инициализации:
// возможны проблемы
double* p1 = new double; // выделение памяти для переменной
// типа double
// без инициализации
double* p2 = new double(5.5); // инициализируем переменную типа double
// числом 5.5
double* p3 = new double[5]; // выделение памяти для массива
// из пяти чисел
// типа double без инициализации
Очевидно, что объявление указателя p0 без инициализации может вызвать проблемы. Рассмотрим пример.
*p0 = 7.0;
![](images/_003.png)
![](images/_001.png)
![](images/_003.png)
В языке С++ нет средства для инициализации массивов объектов встроенных типов, память для которых выделена оператором new. Для массивов работу придется проделать самостоятельно. Рассмотрим пример.
double* p4 = new double[5];
for (int i = 0; i<5; ++i) p4[i] = i;
Теперь указатель p4 ссылается на объекты типа double, содержащие числа 0.0, 1.0, 2.0, 3.0 и 4.0.
Как обычно, мы должны избегать неинициализированных объектов и следить за тем, чтобы они получили значения до того, как будут использованы. Компиляторы часто имеют режим отладки, в котором они по умолчанию инициализируют все переменные предсказуемой величиной (обычно нулем). Это значит, что, когда вы отключаете режим отладки, чтобы отправить программу заказчику, запускаете оптимизатор или просто компилируете программу на другой машине, программа, содержащая неинициализированные переменные, может внезапно перестать работать правильно. Не используйте неинициализированные переменные. Если класс X имеет конструктор по умолчанию, то получим следующее:
X* px1 = new X; // один объект класса Х, инициализированный
// по умолчанию
X* px2 = new X[17]; // 17 объектов класса Х, инициализированных
// по умолчанию
Если класс Y имеет конструктор, но не конструктор по умолчанию, мы должны выполнить явную инициализацию
Y* py1 = new Y; // ошибка: нет конструктора по умолчанию
Y* py2 = new Y[17]; // ошибка: нет конструктора по умолчанию
Y* py3 = new Y(13); // OK: инициализирован адресом объекта Y(13)
17.4.5. Нулевой указатель
Если в вашем распоряжении нет другого указателя, которым можно было бы инициализировать ваш указатель, используйте 0 (нуль).
double* p0 = 0; // нулевой указатель
Указатель, которому присвоен нуль, называют нулевым (null pointer). Корректность указателя (т.е. ссылается ли он на что-либо) часто проверяется с помощью сравнения его с нулем. Рассмотрим пример.
if (p0 != 0) // проверка корректности указателя p0
Этот тест неидеален, поскольку указатель p0 может содержать случайное ненулевое значение или адрес объекта, который был удален с помощью оператора