
五、C++ 11 指针空值—nullptr
5.1、引入nullptr的意义:
在C++程序开发中,为了提高代码的健壮性,一般会在定义指针的同时会完成初始化操作(避免出现野指针),在指针指向尚未明确的情况下,都会给指针初始化为空指针。在C++98/03标准中,将一个指针初始化为空指针的方式有两种:
char *my_ptr = 0;
char *my_ptr = NULL;
在底层源码中 NULL 这个宏是这样定义的:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看出,NULL可能被定义为字面常量0,或者是定义为无类型指针(void*)0
。
原因: 由于 C++ 中, void *
类型无法隐式转换为其他类型的指针,此时使用 0 代替 ((void *)0)
,用于解决空指针的问题。这个 0(0x0000 0000)表示的就是虚拟地址空间中的 0 地址,这块地址是只读的。但是无论采用什么样的定义方式,我们在使用空值的指针时,都不可避免地会遇到一些麻烦;
如下代码:
#include <iostream>
using namespace std;
void fun(char *c)
{
cout << "void func(char *c)" << endl;
}
void fun(int i)
{
cout << "void func(int i)" << endl;
}
int main()
{
// 想要调用重载函数 void func(char *p)
func(NULL); //注:如果使用gcc编译,NULL转化为内部标识 __null,该语句会编译失败
// 想要调用重载函数 void func(int i)
func(0);
func((char*)0);
return 0;
}
输出:
void func(int i)
void func(int i)
void func(char *c)
虽然调用func(NULL)
; 最终链接到的还是void func(int p)
和预期是不一样的;
原因: C++ 中将NULL
定义为字面常量 0,并不能保证在所有场景下都能很好的工作,比如,函数重载时,NULL
和 0 无法区分;
在C++11新标准中,出于兼容性的考虑,字面常量0的二义性并没有被消除;但是新标准为二义性给出了新的答案,就是使用 nullptr
;在C++11 新标准中, nullptr
是一个所谓“指针空值类型”的常量;指针空值类型被命名为 nullptr_t
。
可以在支持 nullptr
的头文件(csddef)中找到如下定义:
typedef decltype(nullptr) nullptr_t;
使用 nullptr_t
的时候必须#include(#include有些头文件也会间接#include,比如),而 nullptr
则不用。
大概就是由于 nullptr
是关键字,而 nullptr_t
是通过 decltype
推导而来的缘故。
对上述程序进行修改:
#include <iostream>
using namespace std;
void fun(char *c)
{
cout << "void func(char *c)" << endl;
}
void fun(int i)
{
cout << "void func(int i)" << endl;
}
int main()
{
//调用重载函数 void func(char *p)
func(nullptr);
//调用重载函数 void func(int i)
func(0);
return 0;
}
结果:
void func(char *c)
void func(int i)
可以看出,nullptr
无法隐式转换为整形,但是可以隐式匹配指针类型。在 C++11 标准下,相比NULL
和 0,使用 nullptr
初始化空指针可以令我们编写的程序更加健壮。
5.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。
5.3、关于nullptr规则的一些讨论
①、在C++11标准中,nullptr
类型数据所占用的内存空间大小跟void*
相同的,即:
sizeof(nullptr_t) == sizeof(void*)
注:nullptr
是否是(void *)0
的一个别名?
不是,尽管两者看起来很相似,都可以被转换为任何类型的指针,但两者在语法层面有着不同的内涵。nullptr
是一个编译时期的常量,它的名字是一个编译时期的关键字,能够为编译器所识别。而(void*)0
只是一个强制转换表达式,其返回的也是一个 void *
指针类型。最为重要的是,在C++语言中,nullptr
到任何指针的转换是隐式的,而 (void*)0
则必须经过类型转换后才能使用。
可以关注公众号:Kevin的嵌入式学习站,创作不易,但您的点赞、关注、收藏就是对我最大的鼓励!