在上一课(《指针简介》)中,我们介绍了指针的基本概念:指针是保存另一个对象地址的对象,可通过解引用运算符 *
访问该地址上的值。例如:
#include <iostream>
int main() {
int x{ 5 };
std::cout << x << '\n'; // 打印变量 x 的值
int* ptr{ &x }; // ptr 保存 x 的地址
std::cout << *ptr << '\n'; // 通过 ptr 解引用打印 x 的值
return 0;
}
输出:
5
5
上一课还提到,指针不一定要指向有效对象。本课将进一步探讨“指向空无一物”的指针及其含义。
空指针(null pointer)
除内存地址外,指针还能保存一个特殊值:空值(null),表示“无值”。当指针持有空值时,称为空指针(null pointer),此时它不指向任何对象。
最简单的创建方式是值初始化:
int main() {
int* ptr{}; // ptr 现在是空指针
return 0;
}
最佳实践
若暂时不打算让指针指向有效对象,请将其值初始化为空指针。
由于指针可通过赋值改变指向,初始为空的指针稍后也可指向有效对象:
int main() {
int* ptr{}; // 空指针
int x{ 5 };
ptr = &x; // 现指向 x
std::cout << *ptr; // 输出 5
}
关键字 nullptr
与布尔字面值 true
/false
类似,nullptr
是 C++11 引入的空指针字面值,用于显式地初始化、赋值或传递空指针:
int main() {
int* ptr{ nullptr }; // 初始化为空
int value{ 5 };
int* ptr2{ &value };
ptr2 = nullptr; // 置空
someFunction(nullptr); // 作为实参传递
}
最佳实践
请使用 nullptr
而非旧式的 0
或 NULL
。
解引用空指针导致未定义行为
与悬垂指针一样,解引用空指针将导致未定义行为,通常表现为程序崩溃:
int main() {
int* ptr{}; // 空指针
std::cout << *ptr; // 未定义行为,极可能崩溃
}
检查空指针
可像对待布尔值一样判断指针是否为空:
int main() {
int x{ 5 };
int* ptr{ &x };
if (ptr == nullptr)
std::cout << "ptr 为空\n";
else
std::cout << "ptr 非空\n";
int* nullPtr{};
std::cout << (nullPtr ? "非空\n" : "空\n");
}
输出:
ptr 非空
空
警告
条件语句只能区分空/非空,无法判断非空指针是否悬垂。
用 nullptr
避免悬垂指针
当对象销毁时,指向它的指针成为悬垂指针;编译器不会自动将其置空。因此,程序员应确保:
- 指针要么指向有效对象,要么显式设为
nullptr
。 - 解引用前检查非空,以规避悬垂或空指针。
旧式空指针字面值:0 与 NULL
旧代码中可见:
float* ptr{ 0 }; // 合法,但不推荐
ptr = 0;
#include <cstddef>
double* ptr2{ NULL }; // 不推荐
现代 C++ 应使用 nullptr
(原因见 12.11 课)。
优先使用引用
引用与指针都能间接访问对象,但引用更安全:
- 不能绑定空值,避免空引用。
- 创建后不可重定向,减少悬垂风险。
若无需指针的额外能力,应优先使用引用。
笑话
你听过空指针的笑话吗?
没关系,你不会“解引用”它的。
测验
问题 1
a) 能否判断指针是否为空?如何?
答:能,用 ptr == nullptr
或隐式布尔转换 if (ptr)
。
b) 能否判断非空指针是否有效?
答:不能,只能保证非空;需人为避免悬垂。
问题 2
判断行为:
a) 将对象地址赋给非常量指针 → 可预测
b) 赋 nullptr
→ 可预测
c) 解引用指向有效对象的指针 → 可预测
d) 解引用悬垂指针 → 未定义
e) 解引用空指针 → 未定义
f) 解引用非空指针 → 可能未定义(若悬垂)
问题 3
为何将不指向有效对象的指针设为 nullptr
?
答:统一用“空”表示无效,解引用前只需检查非空,简化错误检测。