前言
c++11特性引出了nullptr来代替null和0。因此,我们不仅疑惑为什么要代替呢。null和0本身有什么不足缺点吗。为此,特此用这篇博客总结下
一、nullptr和NULL具体实现
在 C 语言中,我们使用 NULL 表示空指针,它实际上是一个宏,具体被定义为:
/* C 语言程序 */
#define NULL ((void*)0)
#define NULL 0 /* 两者皆可 */
因为在C 语言中,是允许 void 指针隐式转换为其它类型指针的,所以 #define NULL ((void*)0) 这样的定义不会有问题。
C++ 语言出现后,为了保持对 C 语言的兼容,保留了 NULL,但对 NULL 的定义变得更为严格:
/* C++ 语言程序 */
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
NULL 被定义为 0,而不是 ((void*)0),因为在 C++ 语言中,void 指针是不可以隐式转换为其它类型指针的,必须显示转换,
/* C++ 语言程序 */
#define NULL ((void*)0) /* 如果在 C++ 语言中这么定义的话 */
int* a = NULL; /* 隐式转换,错误 */
int* a = (int*)NULL; /* 显示转换,正确,但很麻烦,所以 NULL 都会被定义为 0 */
在 C++98 之前(包括 C++98),在对 NULL 的使用上,都一直存在一个问题,假设有下面的代码,
/* C++ 语言程序 */
void func(int i);
void func(char* p);
func(NULL); /* 该调用哪个?*/
NULL 其实就是等于 0,对于上面的两个函数,它都是符合的,如此,就会出现语义二义性的错误。
为了解决上述重载函数所带来的问题,C++11 的 nullptr 应运而生。nullptr 实质上是一个常量,实现代码大致如下,
/* C++ 语言程序 */
const /* 常量 */
class
{
public:
template<class T>
operator T*() const /* 向任意类型的非成员指针转换 */
{
return 0;
}
template<class C, class T>
operator T C::*() const /* 向任意类型的成员指针转换 */
{
return 0;
}
private:
void operator&() const /* 不可取地址 */
{
}
} nullptr = {};
nullptr 只是一个常量,这就意味着我们可以在程序中随意定义一个与其名称相同的标识符,但因为 nullptr 在实际编程中的应用实在太广泛,因此 C++ 编译器一般都会把 nullptr 定为关键字,避免程序员的滥用。
了解了nullptr和NULL的具体实现,接下来我们再讲讲如下内容
二、为什么优先选用nullptr,而非0或NULL
c++98中,NULL可能在指针性别和整型之间进行重载时发生意外。
如果向这样的重载函数传递0和NULL,是从来不会调用到要求指针型别的重载版本的。
void f(int); //f的三个重载版本
void f(bool);
void f(void*);
f(0); //调用的是f(int),而不是f(void*)
f(NULL); //可能通不过编译,但一般会调用f(int)。从来不会调用f(void*)
f(NULL)的不确定性是NULL的型别在实现中的余地的一种反映。
对于C++98的程序员来说,指导原则是不要在指针型别和整型之间做重载。
nullptr的优点在于,它不具备整型型别。实话实说,它也不具备指针型别,但可以把它想成一种任意型别的指针。nullptr的实际型别是std::nullptr_t,并且,在一个漂亮的循环定一下,std::nullptr_t的定义被指定为nullptr的型别。型别std::nullptr_t可以隐式转换到所有的裸指针型别,这就是为何nullptr可以扮演所有型别指针的原因
调用重载函数f时传入nullptr会调用void*那个重载版本(带指针形参的重载版本),因为nullptr无法视作任何一种整型:
f(nullptr); //调用的是f(void*)这个重载版本
因此,使用nullptr而非0和NULL就避免了重载决议中的意外,但这不是它仅有的优点。它还可以提升代码的清晰性,尤其在涉及auto变量时。
如下代码:
auto result=findRecord(/*实参*/);
if(result==0)
{
}
如果刚好不知道(或不容易得出)findRecord的返回值类别的话,那么result是指针型别还是整数型别就不清楚了。 毕竟0(作为findRecord的检测值)两者都有可能。
但是如下代码:
auto result=findRecord(/*实参*/);
if(result==nullptr)
{
}
这会就没有多义性了:result必然具备指针型别
模板型别推导会将0和NULL推导成“错误”型别(即它们的真实型别,而非退而求其次的表示空指针这个意义)。而使用nullptr,模板就不会带来特殊的麻烦。
并且nullptr也不会造成重载决议的问题