配套视频课程已更新完毕,大家可通过以下两种方式观看视频讲解:

关注公众号:爱编程的大丙,或者进入大丙课堂学习。


在C++程序开发中,为了提高程序的健壮性,一般会在定义指针的同时完成初始化操作,或者在指针的指向尚未明确的情况下,都会给指针初始化为NULL,避免产生野指针(没有明确指向的指针,操作也这种指针极可能导致程序发生异常)。C++98/03 标准中,将一个指针初始化为空指针的方式有 2 种:

1
2
char *ptr = 0;
char *ptr = NULL;

在底层源码中NULL这个宏是这样定义的:

1
2
3
4
5
6
7
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

也就是说如果源码是C++程序NULL就是0,如果是C程序NULL表示(void*)0。那么为什么要这样做呢? 是由于 C++ 中,void * 类型无法隐式转换为其他类型的指针,此时使用 0 代替 ((void *)0),用于解决空指针的问题。这个0(0x0000 0000)表示的就是虚拟地址空间中的0地址,这块地址是只读的。

虚拟地址空间结构图

C++ 中将 NULL 定义为字面常量 0,并不能保证在所有场景下都能很好的工作,比如,函数重载时,NULL0 无法区分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

void func(char *p)
{
cout << "void func(char *p)" << endl;
}

void func(int p)
{
cout << "void func(int p)" << endl;
}

int main()
{
func(NULL); // 想要调用重载函数 void func(char *p)
func(250); // 想要调用重载函数 void func(int p)

return 0;
}

测试代码打印的结果为:

1
2
void func(int p)
void func(int p)

通过打印的结果可以看到,虽然调用func(NULL);最终链接到的还是void func(int p)和预期是不一样的,其实这个原因前边已经说的很明白了,在C++中NULL0是等价的。

出于兼容性的考虑,C++11 标准并没有对 NULL 的宏定义做任何修改,而是另其炉灶,引入了一个新的关键字nullptrnullptr 专用于初始化空类型指针,不同类型的指针变量都可以使用 nullptr 来初始化

1
2
3
int*    ptr1 = nullptr;
char* ptr2 = nullptr;
double* ptr3 = nullptr;

对应上面的代码编译器会分别将 nullptr 隐式转换成 int*char* 以及 double* 指针类型。

使用nullptr可以很完美的解决上边提到的函数重载问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

void func(char *p)
{
cout << "void func(char *p)" << endl;
}

void func(int p)
{
cout << "void func(int p)" << endl;
}

int main()
{
func(nullptr);
func(250);
return 0;
}

测试代码输出的结果:

1
2
void func(char *p)
void func(int p)

通过输出的结果可以看出,nullptr 无法隐式转换为整形,但是可以隐式匹配指针类型。在 C++11 标准下,相比 NULL 和 0,使用 nullptr 初始化空指针可以令我们编写的程序更加健壮。