前言
在 c++ 程序中,为了避免野指针等问题,一般在定义指针的同时会完成初始化操作。而当指针不明确时,通常会将其初始化为空。在 c++98/03 标准中,初始化空指针有两种方式:
int* int_ptr = 0;
int* int_ptr = NULL;
而在 c++11 后,引入了 nullptr 关键字,让空指针的初始化更为安全。
1. NULL 的定义
1.1 #define NULL 0
上图是 GNU c++ 12.3.0 中的定义,先不考虑它对 __GNUG__ 和 _WIN64 的处理,将其简化为如下版本:
#ifndef __cplusplus
#define NULL ((void *)0)
#else
#define NULL 0
#endif
换句话说:在 c++ 中 NULL 等价于 0,c 语言中 NULL 等价于 (void*)0。
注意:但在刚才我给的图片可以看出,在 gnu 中 NULL 被定义为 __null。暂且不考虑这个问题,先看简化版本。
为了实现简化版本,在程序中添加如下代码:#undef NULL #define NULL 0
现在有如下两个重载函数:
void func(int* arg) { std::cout << "int*" << std::endl; }
void func(int arg) { std::cout << "int " << std::endl; }
调用 func(NULL),将输出什么呢?结果是 "int " 而非 “int*”,这是显而易见的,因为 NULL 在预处理时被替换为 0。但是这不符合语义:NULL 用于初始化任意类型的空指针,语义上它应该表示空指针。既然是空指针,那么应该调用参数为指针类型的函数:func(int*),然而事实并非如此,编译器面对 0,会先将其视为整型,再视为指针,这导致没有预期调用对应的重载函数。为此,在 c++11 后引入专用于空指针初始化的关键字——nullptr
来解决这一问题。
1.2 #define NULL ((void *)0)
在 c 语言中,NULL 被替换为 (void*(0)),这直接告诉编译器 NULL 就是一个 void 指针。那在 c++ 中为何不沿用这一定义呢?看下面例子:
现在有如下两个重载函数:
void func(int* arg) { std::cout << "int*" << std::endl; }
void func(int arg) { std::cout << "int " << std::endl; }
为了在 c++ 模拟 c 语言中的 NULL,在程序中加入下面语句:
#undef NULL #define NULL ((void *)0)
调用 func(NULL),使用 g++ 运行代码,你将得到如下报错:
没有可以匹配的函数 ‘func(void*)’,也就是说:void* 不能隐式转为其他类型的指针,因此代码会报错。显然在 c++ 这不能定义仍然不能解决空指针初始化的问题:void* 不能直接
用于初始化任意类型的指针。那么怎么解决呢?还是 nullptr
关键字的引入。
1.3 #define NULL __null
在 gnu c++ 中,NULL 被定义为 __null,这不属于 c++ 标准,它是 GNU 的扩展,属于 gnu 编译器的内部实现。
__null: 可以简单看作大小等于 void*,值为 0 的整形数。(详情见 gnu 官网)
#include <iostream>void func(int* arg) { std::cout << "int*" << std::endl; }
void func(int arg) { std::cout << "int" << std::endl; }
void func(long arg) { std::cout << "long " << std::endl; }
void func(long long arg) { std::cout << "long long" << std::endl; }int main()
{func(NULL);std::cout << "__null = " << __null << std::endl;std::cout << "sizeof(__null) = " << sizeof(__null) << std::endl;std::cout << "sizeof(void*) = " << sizeof(void*) << std::endl;return 0;
}
运行结果:
2. nullptr 关键字
在 c++11 后,引入了新的关键字 nullptr
,专用于初始化任意类型的空指针:
int* int_ptr = nullptr;
double* double_ptr = nullptr;
class Person;
Person* person = nullptr;
对应上述代码,编译器内部会将 nullptr 隐式转换为 int*、double*、Person* 类型,这样更为安全,也符合语义要求。
在来调用上述重载函数:func(nullptr),将输出:“int*”。因为 nullptr 只能隐式转为指针类型。