Як і у випадку зі звичайними змінними, вказівники НЕ ініціалізуються при створенні. Якщо значення не було присвоєно, то вказівник за замовчуванням вказуватиме на будь-яку адресу, вмістом якої є сміття.
Нульове значення і нульові вказівники
Крім адрес в пам’яті, є ще одне значення, яке вказівник може зберігати: значення null. Нульове значення (або “null-значення”) — це спеціальне значення, яке означає, що вказівник ні на що не вказує. Вказівник, що містить значення null, називається нульовим вказівником.
У мові C++ ми можемо присвоїти вказівнику нульове значення, ініціалізувавши його/присвоюючи йому літерал 0
:
1 2 3 4 |
int *ptr(0); // ptr тепер є нульовим вказівником int *ptr1; // ptr1 не ініціалізований ptr1 = 0; // ptr1 тепер є нульовим вказівником |
Оскільки значенням нульового вказівника є нуль, то це можна використовувати всередині умовного розгалуження для перевірки того, чи є вказівник нульовим чи ні:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int main() { double *ptr(0); if (ptr) std::cout << "ptr is pointing to a double value."; else std::cout << "ptr is a null pointer."; return 0; } |
Порада: Ініціалізуйте вказівники нульовим значенням, якщо не збираєтесь присвоювати їм інші значення.
Розіменування нульових вказівників
Як ми вже знаємо з попереднього уроку, розіменування вказівників зі сміттям призведе до несподіваних результатів. З розіменуванням нульового вказівника все аналогічно: у більшості випадків ви отримаєте збій в програмі.
У цьому є сенс, адже розіменування вказівника означає, що потрібно «перейти до адреси, на яку вказує вказівник, і дістати з цієї адреси значення». Нульовий вказівник не має адреси, тому й такий результат.
Макрос NULL
В мові програмування Сі (але не в C++) є спеціальний макрос препроцесора з ім’ям NULL, який визначений як значення 0
. Хоча він і не є частиною мови C++, його використання досить поширене, і повинно працювати в кожному компіляторі С++:
1 |
int *ptr(NULL); // присвоюємо адресу 0 вказівнику ptr |
Однак, оскільки NULL є макросом препроцесора і, технічно, не є частиною мови C++, то його не рекомендується використовувати.
Ключове слово nullptr в C++11
Зверніть увагу, значення 0
не є типом вказівника, і присвоювання вказівнику значення 0
для позначення того, що він є нульовим — трохи суперечливо, вам не здається? Дуже рідко, використання 0
в якості аргументу-літералу може призвести до проблем, так як компілятор не зможе визначити, чи використовується нульовий вказівник чи ціле число 0
:
1 |
doAnything(0); // чи є 0 аргументом-значенням чи аргументом-вказівником? (компілятор визначить його як цілочисельне значення) |
Для вирішення цієї проблеми в C++11 ввели нове ключове слово nullptr, яке також є константою r-value.
Починаючи з C++11, при роботі з нульовими вказівниками, використання nullptr є кращим варіантом, ніж використання 0
:
1 |
int *ptr = nullptr; // примітка: ptr як і раніше залишається вказівником типу int, але зі значенням null (0) |
Мова C++ неявно конвертує nullptr в відповідний тип вказівника. Таким чином, у вищенаведеному прикладі, nullptr неявно перетвориться у вказівник типу int, а потім значення nullptr
присвоюється ptr
.
nullptr також може використовуватися для виклику функції (в якості аргументу-літералу):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> void doAnything(int *ptr) { if (ptr) std::cout << "You passed in " << *ptr << '\n'; else std::cout << "You passed in a null pointer\n"; } int main() { doAnything(nullptr); // тепер аргумент є точно нульовим вказівником, а не цілочисельним значенням return 0; } |
Порада: В C++11 використовуйте nullptr для ініціалізації нульових вказівників.
Тип даних std::nullptr_t в C++11
У C++11 додали новий тип даних std::nullptr_t, який знаходиться в заголовковому файлі cstddef. std::nullptr_t може мати тільки одне значення — nullptr
! Хоча це може здатися трохи безглуздим, але це корисно в одному випадку. Якщо вам потрібно написати функцію, яка приймає аргумент nullptr, то який тип параметру потрібно використовувати? Правильно! std::nullptr_t. Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> #include <cstddef> // для std::nullptr_t void doAnything(std::nullptr_t ptr) { std::cout << "in doAnything()\n"; } int main() { doAnything(nullptr); // виклик функції doAnything() з аргументом типу std::nullptr_t return 0; } |
Вам, ймовірно, ніколи це не доведеться використовувати, але знати про це варто (на всяк випадок).