NULL、nullptr、0剪不断理还乱的关系

NULL 和 0 的歧义

先看下NULL的定义,在stddef.h中(/usr/lib/gcc/x86_64-redhat-linux/4.8.2/include/stddef.h)
NULL、nullptr、0剪不断理还乱的关系_第1张图片
在C中,NULL定义为((void*)0)。
在C代码中,一般用NULL表示空指针,即指针的0值。例如:

int *p = NULL;		// 正确
int *p = 0;		// 正确

在C++中,NULL定义为0或者0L。(是0还是0L跟编译器定义的头文件有关)
在C++98代码中,我们也只能用NULL来表示空指针。例如:

#include 
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 
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*)才对,但是实际情况却是编译不通过,如下:
NULL、nullptr、0剪不断理还乱的关系_第2张图片
为什么呢?
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
    inline operator T*() const
        { return 0; }

    template
    inline operator T C::*() const
        { return 0; }
 
private:
    void operator&() const;
} nullptr = {};

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

参考文章和链接

  1. https://www.cnblogs.com/DswCnblog/p/5629073.html
  2. https://www.cnblogs.com/xinxue/p/5415139.html
  3. https://blog.csdn.net/u011304970/article/details/72229013

你可能感兴趣的:(C++,编程语言,C++填坑之旅)