为什么要引入nullptr呢?任何事物的产生都有其缘由和意义,nullptr也不例外。一切还得从0开始说。C++98中的0即表示整数字面值0,又表示指针的0值:

class Foo
{public: void bar() {}
};int *p = 0;  // 0表示普通指针的0值
void (Foo::*pBar)() = 0;  // 0表示指向成员函数的指针的0值
std::shared_ptr<int> pi = 0;  // 0表示智能指针的0值int x = 0;  // 0表示整数0

就算是用NULL表示空指针,也是换汤不换药,C++98规定NULL是一个整型0值,根据不同的编译器实现,可能是0或0L。更尴尬的是,可以拿NULL当合法的整型(int或者long)来使用:

int *q = NULL; // 看起来不错,NULL表示指针的0值
int y = NULL;  // 尴尬
long z = NULL * NULL;  // 尴尬

另外NULL还是个宏,宏的缺点很多,C++并不建议使用宏,而建议使用const变量代替。

关键的问题在于C++98没有一种方法可以区别整数0值和指针0值。硬生生的让0一人分饰两角。在重载整型和指针的场合就会遇到麻烦了:

void foo(int) {}
void foo(int*) {}foo(0);  // 调用foo(int)而不是foo(int*)
foo(NULL);  // 如果NULL是0,则调用foo(int); 如果NULL是0L,则编译错误

foo(0)调用的是foo(int),因为0是int值,与foo(int)严格匹配,当NULL为0时,foo(NULL)也是同样的情况。如果NULL为0L时,则编译错误,因为没有一个函数是严格匹配,0L又同时可以转型为int和int*,编译器无法决定调用哪个函数。因此在C++98中,尽可能不以整型和指针重载函数是一个良好的习惯。

而我们调用foo(NULL)的意图是调用foo(int*),却无论如何也做不到。这就造成了表象(用指针调用)和实质(用整数调用)的不一致,让人混乱且费解。如果需要调用foo(int*),必须将0显示转换成指针:

foo(static_cast<int*>(NULL));  // 调用foo(int*)

这样做麻烦至极。如果能有一个指针字面值表示空指针,就可以避免这样的转换。我们可以自己定义一个类,以实现这个目的(以下代码出自Scott Meyers教授的《Effective C++》第二版):

const // It is a const object...
class nullptr_t
{public:template<class T>inline operator T*() const // convertible to any type of null non-member pointer...{ return 0; }template<class C, class T>inline operator T C::*() const   // or any type of null member pointer...{ return 0; }private:void operator&() const;  // Can't take address of nullptr} nullptr = {};

关键的地方在于这个类可以通过operator T*operator T C::*转换成任何类型的指针,却不能转换成int。引入了这个类和这个类的一个实例nullptr之后,就可以区分整型0值和指针0值了:

foo(0);
foo(nullptr);

这样做很好,但仍需做一些额外的工作,而且C++语言本身没有表示指针0值的东西,这不免有些缺憾。

于是,为了卸下0的重担,为了区别对待整数0值和指针0值,为了填补缺憾, C++11引入了nullptr。

nullptr

C++引入了新的关键字nullptr,表示指针的0值,虽然它不是指针类型,但是它能够转换成任何指针,而且它不能转换成int。

class Foo
{public:void bar() {}
};int *p = nullptr;  // nullptr表示普通指针的0值
void (Foo::*pBar)() = nullptr;  // nullptr表示指向成员函数的指针的0值
std::shared_ptr<int> pi = nullptr;  // nullptr表示智能指针的0值int x = 0;  // 0表示整数0
//int y = nullptr; // 错误!不能将nullptr转换为int值

这样就将整数0值和指针0值区分开了。也可以解决整型和指针重载的问题。

void foo(int) {}
void foo(int*) {}foo(0);  // 调用foo(int)
foo(nulptr);  // 调用foo(int*)

虽然nullptr的引入可以解决整型和指针重载函数的问题,但是C++11没有消除0的空指针性质,因此该问题依然存在,只是在nullptr的掩护下被隐藏了而已。因此尽可能不以整型和指针重载函数仍是一个良好的习惯。

typedef decltype(nullptr) nullptr_t;

std::nullptr_t

nullptr也有其自身的类型,它的类型为std::nullptr_tstd::nullptr_t本身不是指针类型,但是它可以转换为任何指针类型,这也就是nullptr可以看做任何指针类型的原因。它的定义如下:

typedef decltype(nullptr) nullptr_t;

上面是一个很有趣的类型定义。通常情况是先有类型,然后才有该类型的变量。而上面的定义则是,先有一个某类型的字面值,再由这个字面值定义它的类型。妙哉!

可以定义以nullptr_t类型作为形参的函数,该函数只能接指针0值:

void f(std::nullptr_t) {}f(0);
f(nullptr);
f(NULL);int *p = nullptr;
//f(p);  // 错误,p不是指针0值int x = 0;
//f(x); // 错误,x不是指针0值

判断是否是std::nullptr_t

c++14提供了一个类模板来判断一个类型是否是std::nullptr_t:

template< class T >
struct is_null_pointer;

该模板提供了一个bool类型的类静态常量value,表示T是否为std::nullptr_t。并且重载了operator bool和operator()用来返回value的值。

assert(std::is_null_pointer<std::nullptr_t>::value == true);
assert(std::is_null_pointer<const std::nullptr_t>::value == true);
assert(std::is_null_pointer<volatile std::nullptr_t>::value == true);
assert(std::is_null_pointer<const volatile std::nullptr_t>::value == true);
assert(std::is_null_pointer<int *>::value == false);auto nilp = std::is_null_pointer<std::nullptr_t>();
assert(nilp == true);
assert(nilp() == true);

c++17定义了一个常量值is_null_pointer_v以简化操作:

template< class T >
inline constexpr bool is_null_pointer_v = is_null_pointer<T>::value;

用法:

assert(std::is_null_pointer_v<std::nullptr_t> == true);

总结

  • 在需要空指针的场合使用nullptr替代0和NULL。
  • 不以整型和指针重载函数是一个良好的习惯。
  • nullptr的类型是std::nullptr_t。
  • c++11与C++14引入了is_null_pointer和is_null_pointer_v判断一个类型是否是std::nullptr_t。

最新文章

  1. Microsoft 服务器产品端口
  2. “4K云字库”基本框架图
  3. VS2008 包加载失败导致VS2008打开 WebApplication项目时没有设计视图
  4. titanic数据集_TF2.0结构化数据建模流程范例
  5. 自己写的计算时间坐标的代码
  6. 时间同步绝对是一个大问题
  7. C语言中二维数组移动一行,二维数组对每一行进行排序。。
  8. Java 面试之数据库
  9. php中js验证表单,js实现表单验证
  10. golang的指针和切片
  11. atitit.元编程总结 o99
  12. 在线留痕阅卷系统功能说明及其演示
  13. HTML格式及W3C验证工具
  14. 硬盘容量的计算方法,这就是为什么实际容量总比官方标示少的原因
  15. Android Google 账户
  16. ACM常用模板-数据结构
  17. mysql 查询每个科目分数大于80
  18. GT9XX触摸屏双击唤醒功能分析
  19. 23西南大学电子信息专硕经验贴——常见问题
  20. C语言应用宏定义解决三角形的面积问题

热门文章

  1. 美图拟收购乐游科技附属公司约30%股份 后者为Warframe开发商
  2. 一周二次课(12月12日)
  3. 重构——62提炼子类(Extract Subclass)
  4. 跟开涛老师学shiro -- 身份验证
  5. 从零学React Native之11 TextInput
  6. Centos 编译安装mysql 5.6.21
  7. 7、sudo权限问题考察一题(考试题答案系列)
  8. 需求分析报告和需求规格说明书有什么区别
  9. [转]ArcGIS.Server.9.3和ArcGIS API for Flex实现GraphicsLayer上画点、线、面(五)
  10. 3. static file process