我们使用C++编码时,经常会用到指针。在使用指针之前,对指针进行判空操作是一种规范编码的行为。C++中判断指针是否为空有3中方法(NULL, 0, nullptr),我们接下来分别对他们进行比较。
1. NULL
NULL是C语言的内容,在C语言中,NULL的定义为:#define NULL ((void *)0)。因此,我们写 int* p = NULL; 时,NULL实际上是一个void*指针,通过隐式转换成int*指针。但是,采用C++编译器来编译的话是要出错的。因为C++是强类型的,void*不能通过隐式转换成其他类型的指针。
2. 0
由于C++中不能将void*类型的指针隐式转换成其他类型的指针,为了解决空指针的问题,C++引入0来表示空指针。同时也将NULL定义为0。在vcruntime.h头文件中定义如下:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
因此,在C++中NULL就是0,但是绝大多数C++书本都会推荐直接使用0来表示空指针,而不是NULL。为什么呢?接下来,我们举例说明:
// 在test.h中定义两个重载函数
void DoSomething(type1 a, type2* b);
void DoSOmething(type1 a, int i);
// 在Person.cpp中,希望调用 void DoSomething(type1 a, type2* b)
person.DoSomething(a, 0); // 写法1
person.DoSomething(a, NULL); // 写法2
很显然,以上两种写法都不能实现我们的需求。但是,在查找错误的时候,写法1比写法2更直观,更容易被发现问题。写法2中的NULL不够明显,容易让我们失去警觉性。
3. nullptr
虽然上面我们说0比NULL可以让我们更加警觉,但是我们并没有避免这个问题。这个时候C++ 11的nullptr就很好的解决了这个问题,直接使用 DoSomething(a, nullptr); 即可。C++中nullptr的定义如下:
const //修饰类nullptr_t后面定义的nullptr对象
class nullptr_t
{
public:
template<class T> //该模板函数是在重载 类型转换运算符"()"
inline operator T*() const //这是一个类型转换函数,把0的类型转换成T*的类型
{ return 0; } //C++规定转换函数不能写返回类型,
//返回的类型就是operator后面跟的类型
template<class C, class T>
inline operator T C::*() const //T C::*是指向数据成员的指针类型
{ return 0; }
private:
void operator&() const; //??不懂
} nullptr = {}; //= {} 是C++ 11的语法,代表给这个变量初始化
从上述定义可以看出,nullptr是一个“指针空值类型”的常量。”指针空值类型“被命名为nullptr_t,它在stddef.h头文件中定义如下:
#ifdef __cplusplus
namespace std
{
typedef decltype(__nullptr) nullptr_t;
}
using ::std::nullptr_t;
#endif
这里用到了decltype类型说明符,它的作用是选择并返回操作数的数据类型。编译器分析表达式并得到它(__nullptr)的类型,却不实际计算表达式的值。由于nullptr是有类型的,且nullptr类型数据可以隐式转换成任意一个指针类型,因此如果编译器支持nullptr,那我们就优先使用nullptr。