为什么C++引入nullptr ?

简介: 为什么C++引入nullptr ?
C++ 是 强类型语言,void * 是无法隐式转换为别的指针类型的。C++ 里面 void * 指针不能赋值给其他类型指针。

这里面其实有两个问题:


为什么其他指针类型可以隐式转为void *类型,反过来却不允许?

为什么 C++ 必须定义NULL为0,而不能是(void *)0?

很多人只说明了一个,或者都提到但没说具体原因。我们都知道 C 语言中void *和任何指针类型之间可以互相隐式转换:

void*pv0;void*pv1;float*pf;int*pi;pf=pv0;pf=pv1;pi=pv0;pi=pv1;pv0=pf;pv1=pf;pv0=pi;pv1=pi;

然而 C++ 语言中,任何指针类型可以隐式转换转换为void *,反过来则必须强制转换:

void*pv0;void*pv1;float*pf;int*pi;pf=static_cast<float*>(pv0);pf=static_cast<float*>(pv1);pi=static_cast<int*>(pv0);pi=static_cast<int*>(pv1);pv0=pf;pv1=pf;pv0=pi;pv1=pi;

因此,C++ 的NULL不能再定义为(void *)0了。否则,以下代码:

float*pf=NULL;int*pi=NULL;

展开后如下。如果不使用强制转换,将无法通过编译:

float*pf=(void*)0;int*pi=(void*)0;


那么为什么这么设计呢?

为了支持函数重载,这种隐式转换就不能是双向的,严格来讲,单向也不应该允许。

但是 C++ 还是尽可能兼容 C 语言,允许其他类型隐式转换为void *类型。

考虑以下几种场景……


一、其他指针类型隐式转换为void *类型(C/C++):


voidfunc(void*pv){}intmain(intargc,char*arg[]){
  void*pv;
  int*pi;
  // C: yes, C++: yes
  func(pv);
  // C: yes, C++: yes
  func(pi);
  return0;}


C 和 C++ 中都允许,不产生歧义。


二、void *类型隐式转换为其他指针类型(C/C++):


voidfunc(int*pi){}intmain(intargc,char*arg[]){
  void*pv;
  int*pi;
  // C: yes, C++: no
  func(pv);
  // C: yes, C++: yes
  func(pi);
  return0;}


在 C 中允许,C++ 中不允许,看起来也没问题。

很明显,即使 C++ 允许隐式转换也不产生歧义。

别着急,我们再看第三种场景。


三、其他指针类型和void *类型互相隐式转换(C++):


voidfunc(void*pv){}voidfunc(int*pi){}intmain(intargc,char*arg[]){
  void*pv;
  int*pi;
  func(pv);
  func(pi);
  return0;}


当同名函数存在上面这种多个重载版本的情况下,问题就出现了:


func(pv)是匹配void func(void *pv),还是匹配void func(int *pi)?


func(pi)是匹配void func(int *pi),还是匹配void func(void *pv)?


当同名函数有多个重载版本,且参数个数相同,且同一序号的参数存在void *和其他的指针类型的多个重载版本的情况下,如果允许双向隐式转换,那这个重载匹配规则就会乱套。


在 C++中,如果多个重载版本存在包括void *在内的多个类型指针参数的情况下:


func(pv)只能匹配void func(void *pv),而不匹配void func(int *pi)。


func(pi)优先匹配void func(int *pi),其次匹配void func(void *pv)。


所以 C++ 中不允许void *隐式转换为其他指针,归根结底是为了支持函数重载在部分场景下不出现歧义,就必须定义NULL为0而非(void *)0。也就是说,NULL被定义为0完全是躺着中枪。即使允许void *隐式转换为其他指针类型,直接赋值也不存在歧义,只有在进行函数多个重载函数版本匹配时才存在歧义。歧义出现在匹配最佳重载版本的时候,即代码语法分析层面。那么为什么 C++11 又引入nullptr关键字呢?因为NULL被定义为0的确解决了大部分场景下的重载函数匹配问题,但并没有完全解决,比如下面这种场景……


四、指针参数和整型参数的函数重载(C++):


voidfunc(void*pv){}voidfunc(intn){}intmain(intargc,char*arg[]){
  func(NULL);
  func(0);
  return0;}


代码的本意是这样的:


func(NULL)匹配void func(void *pv)

func(0)匹配void func(int n)

事实:


由于NULL被定义为0,所以func(NULL)和func(0)相同。

而func(nullptr)可以匹配void func(void *pv)。


还有下面这种情况:

voidfunc(void*pv){}voidfunc(int*pi){}voidfunc(intn){}intmain(intargc,char*arg[]){
  func(NULL);
  func(0);
  return0;}


你可能以为是这样的:

func(NULL)匹配void func(void *pv)和void func(int *pi)时存在歧义无法编译

func(0)匹配void func(int n)

事实:

同上,由于NULL被定义为0,func(NULL)和func(0)依然相同。

而func(nullptr)则无法通过编译。

归根结底:成也重载,败也重载。

为了支持重载,不允许void *隐式转换为其他指针类型,因而必须将NULL定义为0。

为了支持重载,NULL和0必须区分开来,因此引入了nullptr。

相关文章
|
2天前
|
存储 安全 编译器
【C++专栏】C++入门 | auto关键字、范围for、指针空值nullptr
【C++专栏】C++入门 | auto关键字、范围for、指针空值nullptr
33 0
|
2天前
|
安全 编译器 Linux
【C++练级之路】【Lv.1】C++,启动!(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for,nullptr)
【C++练级之路】【Lv.1】C++,启动!(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for,nullptr)
|
7月前
|
安全 编译器 程序员
C++入门篇---(完)内联函数,auto,for,nullptr
C++入门篇---(完)内联函数,auto,for,nullptr
40 1
|
2天前
|
存储 安全 编译器
【C++11特性篇】盘点C++11中三种简化声明的方式【auto】【decltype】【nullptr】(3)
【C++11特性篇】盘点C++11中三种简化声明的方式【auto】【decltype】【nullptr】(3)
|
7月前
|
程序员 编译器 C语言
【C++】初识C++2(内联函数&auto关键字&范围for循环&nullptr)(下)
【C++】初识C++2(内联函数&auto关键字&范围for循环&nullptr)(下)
|
7月前
|
存储 编译器 C语言
【C++】初识C++2(内联函数&auto关键字&范围for循环&nullptr)(上)
【C++】初识C++2(内联函数&auto关键字&范围for循环&nullptr)(上)
|
2天前
|
存储 安全 编译器
【C++成长记】C++入门 |auto、范围for、nullptr
【C++成长记】C++入门 |auto、范围for、nullptr
|
2天前
|
存储 编译器 Linux
【C++】C++入门第二课(函数重载 | 引用 | 内联函数 | auto关键字 | 指针空值nullptr)
【C++】C++入门第二课(函数重载 | 引用 | 内联函数 | auto关键字 | 指针空值nullptr)
|
5月前
|
存储 安全 编译器
【C++】内联函数、auto关键字、基于范围的for循环、指针空值nullptr
【C++】内联函数、auto关键字、基于范围的for循环、指针空值nullptr
38 0
|
2天前
|
安全 算法 程序员
【C++ 空指针的判断】深入理解 C++11 中的 nullptr 和 nullptr_t
【C++ 空指针的判断】深入理解 C++11 中的 nullptr 和 nullptr_t
64 0