跳到主要内容

C++ 11 nullptr

什么是 nullptr?

在C++11之前,我们通常使用 NULL 或者直接使用 0 来表示空指针。但这种做法存在一些问题,特别是在涉及函数重载和类型推导的场景中。C++11引入了 nullptr 关键字作为空指针字面量,它是一个更加类型安全和语义明确的替代品。

nullptr 的类型是 std::nullptr_t,它可以隐式转换为任何指针类型,但不能转换为整数类型(这与 NULL0 不同)。

nullptr 解决了什么问题?

1. 避免函数重载歧义

在C++11之前,使用 NULL0 作为空指针可能导致函数重载时出现歧义:

cpp
void foo(int i) {
std::cout << "整数版本的foo: " << i << std::endl;
}

void foo(char* p) {
std::cout << "指针版本的foo" << std::endl;
}

int main() {
foo(0); // 调用foo(int),而不是foo(char*)
foo(NULL); // 在许多实现中,NULL被定义为0,所以也会调用foo(int)
return 0;
}

使用 nullptr 可以解决这个问题:

cpp
int main() {
foo(nullptr); // 明确调用foo(char*)
return 0;
}

2. 增强代码的可读性

使用 nullptr 使得代码的意图更加明确——你是想要一个空指针,而不是一个整数0。

3. 提高类型安全

nullptr 不能隐式转换为整数类型,这避免了一些潜在的类型错误。

nullptr 的基本用法

指针初始化

cpp
int* p = nullptr;   // 推荐
int* q = NULL; // 不推荐,但在C++11中仍然有效
int* r = 0; // 不推荐,但有效

指针比较

cpp
int* p = nullptr;
if (p == nullptr) {
std::cout << "p是空指针" << std::endl;
}

// 或者简写为
if (!p) {
std::cout << "p是空指针" << std::endl;
}

函数参数

cpp
void processPointer(int* ptr) {
if (ptr == nullptr) {
std::cout << "收到了空指针" << std::endl;
} else {
std::cout << "收到的值: " << *ptr << std::endl;
}
}

int main() {
int a = 5;
processPointer(&a); // 传递a的地址
processPointer(nullptr); // 传递空指针
return 0;
}

函数返回值

cpp
int* findValue(int arr[], int size, int value) {
for (int i = 0; i < size; i++) {
if (arr[i] == value) {
return &arr[i]; // 找到值,返回指向它的指针
}
}
return nullptr; // 没找到,返回nullptr
}

实际应用案例

案例1:链表中的空节点表示

在链表实现中,我们通常需要表示节点的下一个指针为空:

cpp
struct Node {
int data;
Node* next;

// 构造函数
Node(int val) : data(val), next(nullptr) {}
};

// 使用示例
Node* createLinkedList() {
Node* head = new Node(1);
head->next = new Node(2);
head->next->next = new Node(3);
head->next->next->next = nullptr; // 标记链表结束
return head;
}

案例2:智能指针初始化

在使用智能指针时,我们经常需要创建一个空的智能指针:

cpp
#include <memory>

int main() {
// 创建空的智能指针
std::shared_ptr<int> ptr1 = nullptr;
std::unique_ptr<double> ptr2 = nullptr;

// 检查指针是否为空
if (!ptr1) {
std::cout << "ptr1是空指针" << std::endl;
}

return 0;
}

案例3:返回可选结果的函数

cpp
#include <iostream>
#include <string>

// 尝试查找字符串中的特定字符,如果找到则返回指针,否则返回nullptr
const char* findChar(const std::string& str, char target) {
for (size_t i = 0; i < str.length(); i++) {
if (str[i] == target) {
return &str[i];
}
}
return nullptr;
}

int main() {
std::string message = "Hello, World!";
const char* result = findChar(message, 'W');

if (result != nullptr) {
std::cout << "找到字符: " << *result << std::endl;
std::cout << "位置: " << (result - message.c_str()) << std::endl;
} else {
std::cout << "未找到字符" << std::endl;
}

return 0;
}

nullptr 与 std::nullptr_t

nullptrstd::nullptr_t 类型的一个常量。我们可以自定义接受 std::nullptr_t 类型参数的函数:

cpp
#include <iostream>
#include <cstddef> // 包含std::nullptr_t的定义

void handleNullptr(std::nullptr_t) {
std::cout << "收到了nullptr" << std::endl;
}

int main() {
handleNullptr(nullptr); // 输出: 收到了nullptr
return 0;
}

这种设计使得我们可以为专门处理空指针的情况创建重载函数。

注意事项

警告

尽管 nullptr 提供了更好的类型安全,但在使用指针前仍然需要检查它是否为空,以避免解引用空指针导致的未定义行为。

cpp
int* ptr = nullptr;
if (ptr != nullptr) {
*ptr = 10; // 安全,因为我们已经检查了ptr不是nullptr
} else {
// 处理ptr为nullptr的情况
std::cout << "指针为空,无法赋值" << std::endl;
}
提示

养成使用 nullptr 而不是 NULL0 的习惯,这样可以提高代码的清晰度和安全性。

总结

nullptr 是C++11引入的一个重要特性,它解决了之前使用 NULL0 作为空指针字面量时的一些问题:

  • 提供了更好的类型安全
  • 避免了函数重载时的歧义
  • 增强了代码的可读性和意图表达
  • 引入了专门的空指针类型 std::nullptr_t

在现代C++编程中,推荐始终使用 nullptr 来表示空指针,而不是 NULL0

练习

  1. 编写一个函数,它有两个重载版本,一个接受整数参数,另一个接受整数指针参数。然后使用 nullptrNULL0 分别调用这个函数,观察结果。

  2. 创建一个简单的二叉树节点结构,并实现一个函数来检查节点是否是叶子节点(左右子节点都为 nullptr)。

  3. 编写一个函数模板,该模板可以处理任何类型的指针,并检查它是否为 nullptr

扩展阅读

通过学习和使用 nullptr,你将能够编写更加安全、明确和现代化的C++代码。