7.1.2 nullptr和nullptr_t

C++11标准不仅定义了指针空值常量nullptr,也定义了其指针空值类型nullptr_t,也就表示了指针空值类型并非仅有nullptr一个实例。通常情况下,也可以通过nullptr_t来声明一个指针空值类型的变量(即使看起来用途不大)。

除去nullptr及nullptr_t以外,C++中还存在各种内置类型。C++11标准严格规定了数据间的关系。大体上常见的规则简单地列在了下面:

❑所有定义为nullptr_t类型的数据都是等价的,行为也是完全一致。

❑nullptr_t类型数据可以隐式转换成任意一个指针类型。

❑nullptr_t类型数据不能转换为非指针类型,即使使用reinterpret_cast<nullptr_t>()的方式也是不可以的。

❑nullptr_t类型数据不适用于算术运算表达式。

❑nullptr_t类型数据可以用于关系运算表达式,但仅能与nullptr_t类型数据或者指针类型数据进行比较,当且仅当关系运算符为==、<=、>=等时返回true。

我们可以看看代码清单7-3所示的例子,这个例子集合了大多数我们需要的场景。

代码清单7-3


include <iostream>

include <typeinfo>

using namespace std;

int main()

{

//nullptr可以隐式转换为char*

char*cp=nullptr;

//不可转换为整型,而任何类型也不能转换为nullptr_t,以下代码不能通过编译

//int n1=nullptr;

//int n2=reinterpret_cast<int>(nullptr);

//nullptr与nullptr_t类型变量可以作比较,当使用==、<=、>=符号比较时返回true

nullptr_t nptr;

if(nptr==nullptr)

cout<<"nullptr_t nptr==nullptr"<<endl;

else

cout<<"nullptr_t nptr!=nullptr"<<endl;

if(nptr<nullptr)

cout<<"nullptr_t nptr<nullptr"<<endl;

else

cout<<"nullptr_t nptr!<nullptr"<<endl;

//不能转换为整型或bool类型,以下代码不能通过编译

//if(0==nullptr);

//if(nullptr);

//不可以进行算术运算,以下代码不能通过编译

//nullptr+=1;

//nullprt*5;

//以下操作均可以正常进行

sizeof(nullptr);

typeid(nullptr);

throw(nullptr);

return 0;

}

//编译选项:g++7-1-3.cpp-std=c++11


编译运行代码清单7-3,我们可以得到以下结果:


nullptr_t nptr==nullptr

nullptr_t nptr!<nullptr

terminate called after throwing an instance of'decltype(nullptr)'

Aborted


读者可以对应之前的规则试着分析一下上面的代码为什么有的不能够通过编译,以及为什么会产生上述的运行结果。

注意 如果读者的编译器能够编译if(nullptr)或者if(nullptr==0)这样的语句,可能是因为编译器版本还不够新。老的nullptr定义中允许nullptr向bool的隐式转换,这带来了一些问题,而C++11标准中已经不允许这么做了。

此外,虽然nullptr_t看起来像是个指针类型,用起来更是,但在把nullptr_t应用于模板中时候,我们会发现模板却只能把它作为一个普通的类型来进行推导(并不会将其视为T*指针)。代码清单7-4所示的这个例子来源于C++11标准提案。

代码清单7-4


include <iostream>

using namespace std;

template<typename T>void g(T*t){}

template<typename T>void h(T t){}

int main()

{

g(nullptr);//编译失败,nullptr的类型是nullptr_t,而不是指针

g((float*)nullptr);//推导出T=float

h(0);//推导出T=int

h(nullptr);//推导出T=nullptr_t

h((float)nullptr);//推导出T=float

}

//编译选项:g++7-1-4.cpp-std=c++11


代码清单7-4中,g(nullptr)并不会被编译器“智能”地推导成某种基本类型的指针(或者void*指针),因此要让编译器成功推导出nullptr的类型,必须做显式的类型转换。