1. 从本质上
1) 0是int型的字面值常量
2) NULL 是预处理变量,定义在 cstdlib 中,其值是0
3) nullptr 是 nullptr_t 类型的字面值。
2. cstdlib 中 NULL 的定义
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
之所以这样定义的原因,是因为在C语言中,允许 void* 类型隐式转换为任意指针类型,而C++中不允许这样的强制类型转换,但是可以为任意类型的指针赋0值,因此,在C++中将NULL 定义为0
3. 0 比 NULL 好
在网上的资料中,均是一致认为 0 比 NULL 好,因为很多可能会出错的时候,使用 0 更直观,如:
void test(int i){ cout << "in int" << endl; }
void test(int * i){ cout << "in int *" << endl; }
int main()
{
test(0); //输出 in int
test(NULL); //输出 in int
}
此时,两个函数调用均会调用参数为 int 的 test,使用 0 调动的版本看起来更加直观,因为 0 本身是一个 int 类型的字面值常量,而会潜意识的认为 NULL 应该是一个指针类型,不利于查错
4. nullptr 比 0 更好
nullptr 是 nullptr_t 类型的常量,而该类型定义了转到任意指针类型的转换操作符,同时不允许该类型的对象转换到非指针类型,在上面的调用例子中,使用 nullptr,将会调用第二个函数,输出 “in int*”,因为 nullptr被转换为了int * ,符合我们通常的理解。对于nullptr_t 的类型可以定义如下:
class nullptr_t
{
public:
template<class T>
inline operator T*() const //定义类型转换操作符,使nullptr_t 可转为任意非类成员指针类型
{ return 0; }
//重载类型转换操作符,使 nullptr_t 可以转换为类 C 中任意的指针类型(数据成员指针/函数成员指针)
//对类中数据成员的指针,T 将被推导为数据成员的类型 eg: int (X::*); 此时 T 为 int,C 为 X
//对类中函数成员的指针,T 将被推导为函数成员的类型 eg: int (X::*)(int); 此时T 等效于: typedef int (T)(int)
template<class C, class T>
inline operator T C::*() const
{ return 0; }
private:
void operator&() const;
};
const null_ptr nullptr = {}
5.std::nullptr_t
定义于头文件 | ||
typedef decltype(nullptr) nullptr_t; | (C++11 起) | |
std::nullptr_t 是空指针字面量 nullptr 的类型。它是既非指针类型亦非指向成员指针类型的独立类型。
示例
若二个重载接受不同指针类型,则需要 std::nullptr_t 的重载以接受空指针常量。
#include <cstddef>
#include <iostream>
void f(int* pi)
{
std::cout << "Pointer to integer overload\n";
}
void f(double* pd)
{
std::cout << "Pointer to double overload\n";
}
void f(std::nullptr_t nullp)
{
std::cout << "null pointer overload\n";
}
int main()
{
int* pi; double* pd;
f(pi);
f(pd);
f(nullptr); // 无 void f(nullptr_t) 可能有歧义
// f(0); // 歧义调用:三个函数全部为候选
// f(NULL); // 若 NULL 是整数空指针常量则为歧义
// (如在大部分实现中的情况)
}
输出如下:
Pointer to integer overload Pointer to double overload null pointer overload
总结:
nullptr | 指定空指针值的指针字面量(C++11) |
实现定义的空指针常量 (宏常量) | |
(C++14) | 检查类型是否为 std::nullptr_t (类模板) |