引用的概念
引用,翻译自英文 reference 。 在语法概念上讲,引用并非新定义一个变量,而是给已有变量取了一个别名,它和它引用的变量都是标识同一块内存空间,编译器并不会给引用变量开辟内存空间。也就是说,对引用操作与对变量直接操作是完全等价的。
其代码格式是:类型& 引用变量名(或对象名) = 引用实体 , 比如int& rc = c 。
打个比方,你给你的好友起了个外号,这个外号可以认为是你好友的别名。你好友的名字和别名都是指你好友,你用名字去叫他做事,他会去做事,用外号去叫他做事,他也会去做事(用外号和用名字在效果上是等价的)。
引用是已有变量的一个别名,也就是说,它的存在具有依附性,所以引用在定义时必须初始化(先有变量a,再有这个变量a的别名,也就是说别名是依附于已有实体的),而且其引用的对象在其整个生命周期中不能被改变,即引用只能依附于同一个变量,也就是说,它在初始化时就必须代表某个变量,并且在以后都表示它,不能被改变,但是一个变量可以有多个引用,就好比一个人可以有多个外号。
int& ra;
int b = 3;
int& rb = b;
int& rrb = b;
int d = 10;
int& rd = d;
cout << &d << endl;
cout << &rd << endl;
运行的结果是:它们两个的地址是完全一样的!
注意:引用类型必须和引用实体是同种类型,这个也很好理解。
你会误解下面这段代码吗?
int x = 15;
int& r = x;
int y = 7;
r = y;
其实 r = y 这条语句的意思是:把 y 的值赋给 r (即 x )。 时刻记住:引用只是别名,而且引用只能依附于同一个变量,不能被改变。 其实如果为了更好理解的话,你把 r 全替换成 x 就好了,这样更清晰明了。 在调试状态下观察这四条语句执行完后的结果:
引用的特点: 1)引用在定义时必须初始化。 2)引用一旦引用一个实体,就不能再引用其他实体。 3)一个变量可以有多个引用。
如何使用引用
1)作为参数
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
void Modify(string& s)
{
}
2)作为返回值
int& func()
{
static int n = 0;
return n;
}
string& operator+=(char ch)
{
}
注意:如果函数返回的对象出了函数作用域后仍然存在,则可以使用传引用返回。 如果函数返回的对象在调用函数完成后被销毁,则必须使用传值返回。
像上面的 func 函数和 operator+= 函数,代码都是正确的。
来看看下面这段错误的代码:
int& Sub(int a, int b)
{
int c = a - b;
return c;
}
由于变量 c 的生命周期只在函数内,函数调用完成后就会被销毁,所以不应该用传引用返回,而应该用传值返回。
传引用和传值的区别
先看它们各自的特点:
传值:以值作为参数或返回值类型,在传参和返回时都会产生临时拷贝。
传引用:以引用来作为参数或返回值类型,由于引用都是指已有实体,标识已有的同一块空间,故不会产生像传值那样的临时拷贝。
结论:传引用比传值的效率更高(参数或者返回值类型越大,传值传参的效率就会越低)。
引用和指针的对比
前面我们已经说过,在语法概念上讲,引用并没有自己的独立空间,它和它引用的实体是共用同一块内存空间的。
但在底层实现上,使用引用也会开辟空间,这是因为引用是按照指针的方式来实现的。
int x = 4;
int& rx = x;
rx = 15;
int x = 4;
int* px = &x;
*px = 15;
让我们在VS上对这两段代码进行调试,从反汇编代码的层面上来对比引用和指针:
由此,可以认为,引用在实际的底层实现方式跟指针是一样的。
引用和指针总结:
引用和指针的不同点: 1)引用在定义时必须初始化,而指针没有这个硬性要求(但指针建议初始化,避免野指针)。 2)引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任意一个同类型的 实体。 3)没有NULL引用(有实体,才有别名),但有NULL指针。 4)在 sizeof 中含义不同:sizeof(引用)等于引用实体类型的大小,sizeof(指针)始终是指针(地址空间)的大小(32位下占 4 Byte,64位下占 8 Byte)。 5)引用的自加自减即引用实体的自加自减,指针的自加或自减即指针向后或向前偏移一个类型的大小。 6)有多级指针,但是没有多级引用。 7)访问实体的方式不同,访问引用就是访问实体,而访问指针所指的实体则需要指针显式解引用才能访问。 8)在使用时,引用相对于指针而言更加安全简便。 9)引用会进行类型检查,但指针不会进行检查(这也是为什么指针容易出现野指针和空指针的问题)。
引用和指针的关联: 1)引用在实际的底层实现方式跟指针是一样的。 2)引用可以认为是 const 类型的指针(即指向已经定死,不会发生改变)。
在 C++ 中建议减少指针的使用,增加引用的使用。 这是因为:在使用上,引用比指针更加方便简洁,并且引用会进行类型检查;虽然指针相对于引用更加灵活,但同时也意味着指针更不受约束,很容易出现各种问题。使用引用可以在一定程度上避免很多指针相关的问题。
理解引用时,直接从语法层面上理解即可,没有必要从底层去理解。
关于引用符号和取地址符号
int a = 9;
int& ra = a;
int* p = &ra;
注意区分引用符号和取地址符号(其实也不难区分): & 如果总是跟在类型的后面(像 double& 、int & ),那么就是引用符号,其余情况下均是取地址符号。 引用符号跟取地址符号刚好是同一个(&)而已,不要混淆。
关于为什么 C++ 的引用符号和取地址符号都是 & 的原因(个人猜测): 为了表示引用跟指针有关系,同时也为了说明 C++ 的引用符号是继承自 C 的取地址符号,所以 C++ 的引用符号才用 & 。
|