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。