📝引用
🌠引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
定义:类型&引用变量名(对象名) = 引用实体;
例如以下代码,在变量名前加一个
&
,意思是一个引用类型,b
是a
的别名,也就是a
有了一个外号,但还是a
本身:int a = 70; int& b = a; //引用:b是a的别名
我们接下来看看引用后的地址是否会发生改变:
例如以下例子:
int main() { int a = 70; int& b = a; //引用:b是a的别名 int& c = a; //引用:c是a的别名 c = 80; cout << a << endl; cout << &a << endl; cout << &b << endl; cout << &c << endl; return 0; }
代码运行图:在这个代码中,定义了一个变量a为70,int& b = a; 这里b是a的引用,给a取了一个外号b,int& c = a; 这里c是a的引用,又给a取了一个外号是c,因此我们对c还是对b进行修改,a都会发生改变,这是因为编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
注意:引用类型必须和引用实体是同种类型的
🌉引用特性
- 引用必须在定义时初始化:
引用必须在定义时初始化,不能在之后单独为它赋值。
int a = 10; int& ra = a; // 正确,ra初始化为a int& ra; // 错误,引用必须在定义时初始化
- 一个变量可以有多个引用
int a = 10; int& ref1 = a; int& ref2 = a; ref1++; // a的值变为11 cout << a << endl; // 输出11 ref2--; // a的值变为10 cout << a << endl; // 输出10
引用一旦引用一个实体,再不能引用其他实体
引用本质上就是给原变量添加一个别名,它的内存地址就是原变量的地址。所以对引用赋值或修改,实际上就是修改原变量。而指针不同,指针可以改变指向的对象:一级指针可以改变指向,如p可以从指向a改为指向其他变量,二级指针可以改变一级指针指向的地址,如pp可以改变p指向的地址
而引用更像一个const指针:定义后不能改变指向的对象,就像const指针定义后不能改变指向
但可以通过这个“const指针”来修改原对象,就像通过const指针可以修改原对象
int a = 10; int b = 20; int& ref = a; ref = b; // 错误!引用ref已经引用a,不能再引用b cout << ref << endl; // 输出10,ref依然引用a
如图:ref引用了a,这里的值发生改变是因为b赋值给了ref
🌠使用场景
🌉做参数(传值与传地址)
当引用用来做参数时将会对代码进行大大的优化,并且具有可读性,如:当我们看了很多遍的交换了两个数的函数:
voidvoid Swap(int* a, int* b) { int tmp = *a; *a = *b; *b = tmp; } int main() { int ra = 88; int rb = 99; Swap(&ra, &rb); return 0; }
形参是实参的一份临时拷贝,所以如果想交换需要,传地址,不能传值。
void Swap(int& a, int& b) { int tmp = a; a = b; b = tmp; } int main() { int ra = 88; int rb = 99; Swap(ra, rb); return 0; }
a和b分别是ra和rb的别名,当你调换a和b的纸时,其实是修改了ra和rb的地址的值,这样的好处就是,当你看代码时,引用a和b给人一种感觉,就是操作ra和rb本身。这隐藏了底层是通过地址操作原变量ra和rb的实现细节。从使用者的角度看,代码读起来就像直接交换ra和rb,而不是通过复杂的地址操作实现。
这里使用了引用挺好的,不用担心指针的解引用,地址相关操作,但是,前面我们知道,引用一旦指向一个实体,就无法改变指向,例如,有关链表操作,当我们要删除一个节点,是不是要改变前面节点的指针,让他指向后面节点,而引用恰恰不能改变,因此,引用也不是完全替代指针的
回归正题,这里还有一个小注意点:作用域的不同,因此,在Swap函数里,取别的名字都可以,任由发挥,结果都相同。
void Swap(int& x, int& x) { int tmp = x; x = y; y = tmp; } int main() { int ra = 88; int rb = 99; Swap(ra, rb); return 0; }
🌉传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
#include <time.h> struct A { int a[10000]; }; void TestFunc1(A a) {} void TestFunc2(A& a) {} void TestRefAndValue() { A a; // 以值作为函数参数 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 以引用作为函数参数 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分别计算两个函数运行结束后的时间 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl; } int main() { TestRefAndValue(); return 0; }
按值传递(TestFunc1):
调用TestFunc1(a)时,会将a进行拷贝,生成一个临时对象**a_copy**。**a_copy**作为参数传递给TestFunc1。TestFunc1内部操作的实际上是a_copy,对a_copy的修改不会影响实参a。TestFunc1返回时,临时对象a_copy会被销毁。TestFunc1以值方式传递结构体A作为参数。这会导致每次调用都会对A进行值拷贝,对于一个包含10000个int成员的大结构体,拷贝开销很大。
按引用传递(TestFunc2):
调用TestFunc2(a)时,不会进行值拷贝,直接传递a的引用。TestFunc2内部操作的仍然是实参a本身。TestFunc2返回时,不需要销毁任何对象。TestFunc2以引用方式传递A。这种方式下,函数内直接操作的就是实参a本身,不会有任何拷贝开销。
总结:
TestFunc1
值传递,效率低是因为值拷贝开销大
TestFunc2
引用传递,效率高是因为避免了值拷贝,直接操作的就是实参a
本身
通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大。
我的C++奇迹之旅:值和引用的本质效率与性能比较2:https://developer.aliyun.com/article/1474578