0%

C++指针与引用的区别

先写出两者的3点区别,后面再具体分析:

  1. 当引用被创建时,它必须被初始化,而指针则可以在任何时候被初始化
  2. 一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用,而指针可以在任何时候指向另一个对象
  3. 不可能有NULL引用,必须确保引用是和一块合法的存储单元关联,而指针可以初始化为NULL

从概念上讲,指针(pointers)从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变,这点后面再详细叙述。

而引用(references)是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的,自始至终只能依附于同一个变量。

1
2
3
4
5
6
7
8
9
10
string s1("sunburn");
string s2("saltwater");
string &rs = s1; //引用,rs代表s1
string *ps = &s1; //指针,ps指向s1
rs = s2; //rs仍然是代表s1,但是s1的值现在变成了"saltwater"
//rs一直依附于s1,rs的值就是s1的值

*ps = "owl city"; //修改ps指向的地址中所存放的数据,也就是s1变成了"owl city"
//rs依附于s1,那么这里rs也会变成"owl city"
ps = &s2; //修改ps所指向的地址,ps现在指向s2,s1没有变化

从上面可以得知,指针可以被重新赋值,指向另一个对象,引用却总是代表它最初获得的那个对象。

一般而言,当你需要不指向任何对象时,或是需要在不同时间指向不同对象时,你就应该采用指针,前一种情况你可以将指针设为NULL,后一种情况你可以改变指针所指对象(地址)。而当你确定总是会代表某个对象,而且一旦代表了该对象就不再改变,就应该选用引用。

有一点最基本的,那就是没有所谓的空引用(NULL references),一个引用必须总是代表一个对象,所以如果你有一个变量,其目的是用来指向(代表)另一个对象,但是也可能它不指向任何对象,那么你就应该使用指针,如果这个变量必须总是代表一个对象,并且不允许这个变量为NULL,那么就应该选用引用。说白了,就是C++允许空指针,但不允许空引用。

引用一定得代表某个对象,因此要求引用必须有初值,但是对指针则没有这样的要求

1
2
3
4
5
6
string s1("sunburn");
string s2("saltwater");
string &rs = s1; //初始化
string &rs; //未初始化,error
string *ps; //未初始化的指针,有效,但是风险高,
//实际上,在应用中是不允许的

没有所谓的空引用,这就意味着当使用引用传递形参参数时,则不需要测试其参数的有效性,相反如果使用指针,通常就得测试它是否为NULL。

下面看看在C++中,指针和引用在函数参数传递中的区别:

说到指针传递,以前在C中经常同值传递进行区别,其实指针传递本质上也是值传递的方式,只不过传递的是一个地址值,值传递过程中,被调函数的形参作为被调函数的局部变量处理,即在栈中开辟内存空间存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形参的任何操作都是作为局部变量处理的,其值都存放在栈中,随着被调函数的结束而消亡,不会影响主调函数中实参变量的值,指针传递过程中的实参变量指的则是实参指针,指针传递的目的是通过被调函数去修改实参变量,那么被调函数内部的操作肯定是解指针操作,也就是透过传过来的指针的副本来操作其指向的变量(实参变量)。

而在引用传递过程中,被调函数的形式参数自然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址,然后被调函数对形参的任何操作都被处理成间接寻址,通过栈中存放的地址访问主调函数中的实参变量。这个和前面的说到的指针传递就相同了,都是通过栈中的地址访问主调函数的实参变量,只不过引用传递则不需要指针的解引用操作,直接对形参的操作都会被内部解析为通过地址访问实参变量,从而去影响主调函数中的实参变量。但从这点来看,引用传递其实也是指针传递,只不过这一切实现都隐藏在内部,由编译器完成。

我们对比看下指针传递和引用传递的反汇编代码:

img

上面这两个简单的被调函数都是实现相同的功能,代码的实现上存在本质区别,一个是指针传递一个是引用传递,但是内部的操作,从其反汇编代码来看(红色框框内),两者的汇编代码是一模一样的。但是从代码的实现上来看,引用传递显得更为高效和简洁,无需检查传入参数的有效性,并且不会带入指针这个易错量。

注意的是上面说的一模一样是针对指针传递是以透过指针变量来操作实参变量的目的。当然,如果单一的操作指针变量,没有解引用操作,也就是只改变被调函数中的指针地址,那么它将影响不到主调函数中的相关变量。

1
str1 = str2;    //这里指针变量作为局部变量,不会影响到主调函数的实参变量

当然,如果实际上应用指针传递形参的话,是不会这样去操作的。

其实二者都是地址的概念。指针指向一块内存,它的内容是所指内存的地址,而引用则是某块内存的别名,既然是别名,那么在参数传递时,引用并不会产生对象的副本,对象无需复制,效率更高,以及在函数的返回值上,返回一个对象的引用有时也是必须的,尤其在某些运算符的重载上。