9.9 – Нулевые указатели

Добавлено 7 июня 2021 в 04:31
Глава 9 – Массивы, строки, указатели и ссылки  (содержание)

Нулевые значения и нулевые указатели

Как и обычные переменные, указатели не инициализируются при создании экземпляров. Если указателю значение не присвоено, он по умолчанию будет указывать на какой-то мусорный адрес.

Помимо адресов памяти, есть еще одно дополнительное значение, которое может содержать указатель: нулевое значение. Нулевое значение – это специальное значение, которое означает, что указатель ни на что не указывает. Указатель, содержащий нулевое значение, называется нулевым указателем.

В C++ мы можем присвоить указателю нулевое значение, инициализировав или присвоив ему литерал 0:

float* ptr { 0 };  // ptr теперь является нулевым указателем
 
float* ptr2; // ptr2 не инициализирован
ptr2 = 0;    // ptr2 теперь нулевой указатель

Указатели преобразуются в логическое значение false, если они равны нулю, и в логическое значение true, если они не равны нулю. Следовательно, мы можем использовать условное выражение, чтобы проверить, является ли указатель нулевым или нет:

double* ptr { 0 };
 
// указатели преобразуются в логическое значение false, если они равны нулю,
// и в логическое значение true, если они не равны нулю
if (ptr)
    std::cout << "ptr is pointing to a double value.";
else
    std::cout << "ptr is a null pointer.";

Лучшая практика


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

Косвенное обращение через нулевые указатели

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

Концептуально в этом есть смысл. Косвенное обращение через указатель означает «перейти по адресу, на который указывает указатель, и получить доступ к значению там». У нулевого указателя нет адреса. Что делать, когда вы пытаетесь получить доступ к значению по этому адресу?

Макрос NULL

В C++ есть специальный макрос препроцессора под названием NULL (определен в заголовке <cstddef>). Этот макрос был унаследован от C, где он обычно используется для обозначения нулевого указателя.

#include <cstddef> // для NULL
 
double* ptr { NULL }; // ptr - нулевой указатель

Значение NULL определяется реализацией, но обычно определяется как целочисленная константа 0. Примечание. Начиная с C++11, NULL можно определить как nullptr (что мы обсудим позже).

Лучшая практика


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

Опасности использования 0 (или NULL) для нулевых указателей

Обратите внимание, что значение 0 не является типом указателя, поэтому присвоение 0 (или NULL, до C++11) указателю для обозначения того, что указатель является нулевым, немного противоречиво. В редких случаях, когда ноль используется в качестве литерального аргумента, это может даже вызвать проблемы, потому что компилятор не может определить, имеет ли мы в виду нулевой указатель или целое число 0:

#include <iostream>
#include <cstddef> // для NULL
 
void print(int x)
{
	std::cout << "print(int): " << x << '\n';
}
 
void print(int* x)
{
	if (!x)
		std::cout << "print(int*): null\n";
	else
		std::cout << "print(int*): " << *x << '\n';
}
 
int main()
{
	int* x { NULL };
	print(x); // вызывает print(int*), потому что x имеет тип int*
	print(0); // вызывает print(int), потому что 0 - целочисленный литерал
	print(NULL); // скорее всего, вызывает print(int), хотя мы, вероятно, хотели print(int*)
 
	return 0;
}

В вероятном случае, когда NULL определяется как значение 0, print(NULL) вызовет print(int), а не print(int*), как вы могли бы ожидать от литерала нулевого указателя.

nullptr в C++11

Для решения вышеуказанных проблем в C++11 введено новое ключевое слово nullptr. nullptr – это ключевое слово, очень похожее на логические ключевые слова true и false.

Начиная с C++11, когда нам нужен нулевой указатель, следует отдавать предпочтение ему, а не нулю:

int* ptr { nullptr }; // примечание: ptr по-прежнему является указателем int,
                      // просто он установлен в нулевое значение

C++ неявно преобразует nullptr в любой тип указателя. Итак, в приведенном выше примере nullptr неявно преобразуется в указатель int, а затем значение nullptr присваивается ptr. Это приводит к тому, что ptr, указатель int, становится нулевым указателем.

Мы также можем вызвать функцию с литералом nullptr, который будет соответствовать любому параметру, принимающему значение указателя:

#include <iostream>
 
void print(int x)
{
	std::cout << "print(int): " << x << '\n';
}
 
void print(int* x)
{
	if (!x)
		std::cout << "print(int*): null\n";
	else
		std::cout << "print(int*): " << *x << '\n';
}
 
int main()
{
	int* x { nullptr };
	print(x); // вызывает print(int*)
 
	print(nullptr); // вызывает  print(int*), как и ожидалось
 
	return 0;
}

Для продвинутых читателей


Функция со списком других параметров является новой функцией, даже если функция с таким же именем существует. Мы рассмотрим это позже (10.7 – Перегрузка функций).

Лучшая практика


Используйте nullptr для инициализации указателей нулевым значением.

std::nullptr_t

В C++11 также представлен новый тип std::nullptr_t (в заголовке <cstddef>). std::nullptr_t может содержать только одно значение: nullptr! Хотя это может показаться странным, в одной ситуации это полезно. Если мы хотим написать функцию, которая принимает только аргумент nullptr, какого типа мы сделаем параметр? Ответ – std::nullptr_t.

#include <iostream>
#include <cstddef> // для std::nullptr_t
 
void doSomething(std::nullptr_t ptr)
{
    std::cout << "in doSomething()\n";
}
 
int main()
{
    doSomething(nullptr); // вызываем doSomething с аргументом типа std::nullptr_t
 
    return 0;
}

Возможно, вам никогда не понадобится это использовать, но на всякий случай знать полезно.

Теги

C++ / CppLearnCppNULLnullptrstd::nullptr_tДля начинающихНулевой указательОбучениеПрограммированиеУказатель / Pointer (программирование)

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.