NULL 和 0 的歧义
先看下NULL的定义,在stddef.h中(/usr/lib/gcc/x86_64-redhat-linux/4.8.2/include/stddef.h)
在C中,NULL定义为((void*)0)。
在C代码中,一般用NULL表示空指针,即指针的0值。例如:
int *p = NULL; // 正确
int *p = 0; // 正确
在C++中,NULL定义为0或者0L。(是0还是0L跟编译器定义的头文件有关)
在C++98代码中,我们也只能用NULL来表示空指针。例如:
#include <iostream>
using namespace std;
class Test
{
public:
void func()
{}
};
int main()
{
int *p = NULL; // NULL表示普通指针的0值,即空指针
int *p1 = 0; // 同上
void (Test::*p_func)() = NULL; // NULL表示指向成员函数的指针的0值,函数指针
void (Test::*p1_func)() = 0; // 同上
}
// compile cmd
// g++ -g -std=c++11 main.cpp -o main
可能有人会问,为什么C中(void*)0是空指针常量,而C++中不是?
因为C语言中任何类型的指针都可以(隐式地)转换为void型,反过来也行,而C++中void型不能隐式地转换为别的类型指针(例如:int*p = (void*)0;使用C++编译器编译会报错)。
到现在为止,似乎一切看起来都还挺好的,没什么歧义啊。那我们现在考虑C++函数重载的一种情况,如下:
#include <iostream>
using namespace std;
class Test
{
public:
void func(int a)
{
cout << "call int.." << endl;
}
void func(bool b)
{
cout << "call bool.." << endl;
}
void func(void* c)
{
cout << "call void*.." << endl;
}
};
int main()
{
Test a;
a.func(0); // 调用func(int)
a.func(NULL); // 混乱,且编译不通过
}
// compile cmd
// g++ -g -std=c++11 main.cpp -o main
按照我们编码的意思,a.func(NULL)应该会调用func(void*)才对,但是实际情况却是编译不通过,如下:
为什么呢?
1、a.func(0)这个没问题,是因为0被编译器正确地、严格地认为是int型,所以严格调用了func(int)。
2、但是NULL被认为是0L,这时候没有一个函数能严格匹配,所以0L就要被转换,但是转换成int、bool、void*都可以哪,所以编译器就混乱了,不知道你到底想要调用哪个函数。
解决方案有三种:
case1:再定义一个func(long)函数,就可以使得NULL严格地和long匹配。
void func(long d)
{
cout << "call long.." << endl;
}
但是,这时候编译还是有warning,如下:
case2:就是不要让广义整型数和指针作为区分函数重载的参数(这是C++98一个好的编程习惯)。
case3:引入nullptr。
引入nullptr
就是为了解决上面函数重载时候的问题,C++11中引入了nullptr关键字。
nullptr关键字用于标识空指针,是std::nullptr_t类型的(constexpr)变量。它可以转换成任何指针类型和bool布尔类型(主要是为了兼容普通指针可以作为条件判断语句的写法),但是不能被转换为整数。也正是因为nullptr不能转换为整型数,才区分了他和0(NULL)。
char *p1 = nullptr; // 正确
int *p2 = nullptr; // 正确
bool b = nullptr; // 正确. if(b)判断为false
int a = nullptr; // error
自己实现nullptr
在没有C++11时,没有nullptr的时候,我们应该怎么办呢?
// 出自《Effective C++》第二版
const
class nullptr_t
{
public:
template<class T>
inline operator T*() const
{ return 0; }
template<class C, class T>
inline operator T C::*() const
{ return 0; }
private:
void operator&() const;
} nullptr = {};
关键的地方在于这个类可以通过operator T*和operator T C::*转换成任何类型的指针,却不能转换成int。引入了这个类和这个类的一个实例nullptr之后,就可以区分整型0值和指针0值了。