为什么C++语言中既有指针也有引用
写在前面的话,本文是基于C++03写的。并不涉及C++11里面的概念,比如Rvalue references
引用和指针有何区别?这是个经常会被提到的问题。也有诸多的博客来分析其实引用就是指针。
即使两者的底层实现都一样,都是基于地址,然而我认为这两者是截然不同的概念。
指针,算是一种变相的“引用”,但依然是call by value。区别在于:指针需要函数的调用者显式地来表示“引用”,比如说使用取地址符(&)把变量转换成指针。
引用,是call by reference,区别在于:引用不需要函数的调用者显式的来表示。根据所调用的函数,变量的名字可以分别解释为value或者reference.
不过本篇博客,并不是用来谈两者的区别或者实现。而是说为什么C++同时需要这两个看起来很像的东西。
问题分两方面:
第一,为什么有了引用还需要指针呢?
这个比较简单,因为Bjarne Stroustrup要让C++借C的势,必然要支持指针了。
第二,为什么有了指针还需要引用呢?
这个就比较复杂了,首先一开始C++是没有引用的。比如说:this,被定义为了指针,而不是引用。后来加入了引用的原因主要是为了支持operator overloading。这在Bjarne Stroustrup那本书《The Design and Evolution of C++》和他的C++ FAQ里面都提到了。
细说开来,假设C++里面没有引用,而只有指针,会有什么样的问题。
如果用指针来overload operator的话,就像:
ClassFoo operator+( const ClassFoo *, const ClassFoo * )
使用的时候只能是 &a + &b,这和我们通常的写法 a + b 不太一样,看上去不太习惯。
还可以进一步假设,也可以就写成 a + b,让编译器来根据operator+的类型来解释a和b的类型。如果operator+的参数是值,那就解释成值;如果是指针,就解释成地址。这和现在C++里面把名字解释成值或者引用是一个道理。
这样做的后果是,指针和值不能重载了,比如说下面这两个函数,就无法通过编译。因为可以把a和b同时解释成value和pointer。
void foo(ClassFoo*);void foo(ClassFoo);
针对同一个函数,根据是value还是pointer有不同的实现,这是个相对普遍的需求。而且在C语言中,指针已经有了明确的定义,并不适合赋予它其他的概念。
而引入引用的概念,既可以满足overload operator,也不失重载value和pointer的灵活性。虽然引用也面对一个问题,即值和引用不能重载了,不过也没什么情况需要重载这两者。
除了支持operator overloading,引用还带来一个指针无法替代的特性: 引用临时对象。因为引用必须在定义的时候就赋值,以后无法更改。比如下面的代码
class ClassFoo{public :ClassFoo( const char *){};};const ClassFoo* g_ClassFoo = NULL;void foo(const ClassFoo& r){};void bar(const ClassFoo* p){g_ClassFoo = p;};
foo( "abc" );bar( "def" );
foo的调用是没有问题的,"abc"生成的临时对象只会存在于函数的调用期间。
bar的调用是有问题,指针没法指向临时对象的,因为指针是可以把这种关系传递出去的。
综上所述,C++必须同时有指针和引用这两个概念。
顺便提一下,还有个被问到烂的问题,指针和引用那个好,该用哪一个?
可以参考,Google C++ Style Guide 和 c++-faq-lite,我就不吐槽了。