C++11nullptr
为什么要引入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_t
。std::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。
最新文章
- Microsoft 服务器产品端口
- “4K云字库”基本框架图
- VS2008 包加载失败导致VS2008打开 WebApplication项目时没有设计视图
- titanic数据集_TF2.0结构化数据建模流程范例
- 自己写的计算时间坐标的代码
- 时间同步绝对是一个大问题
- C语言中二维数组移动一行,二维数组对每一行进行排序。。
- Java 面试之数据库
- php中js验证表单,js实现表单验证
- golang的指针和切片
- atitit.元编程总结 o99
- 在线留痕阅卷系统功能说明及其演示
- HTML格式及W3C验证工具
- 硬盘容量的计算方法,这就是为什么实际容量总比官方标示少的原因
- Android Google 账户
- ACM常用模板-数据结构
- mysql 查询每个科目分数大于80
- GT9XX触摸屏双击唤醒功能分析
- 23西南大学电子信息专硕经验贴——常见问题
- C语言应用宏定义解决三角形的面积问题
热门文章
- 美图拟收购乐游科技附属公司约30%股份 后者为Warframe开发商
- 一周二次课(12月12日)
- 重构——62提炼子类(Extract Subclass)
- 跟开涛老师学shiro -- 身份验证
- 从零学React Native之11 TextInput
- Centos 编译安装mysql 5.6.21
- 7、sudo权限问题考察一题(考试题答案系列)
- 需求分析报告和需求规格说明书有什么区别
- [转]ArcGIS.Server.9.3和ArcGIS API for Flex实现GraphicsLayer上画点、线、面(五)
- 3. static file process