🍪四、引用的使用场景
🍿1、做参数(传引用传参)
🌰举个栗子👇
void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } int main() { int a = 10; int b = 20; Swap(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; }
🍿2、做返回值(引用做返回值)
先看一下传值返回👇
//传值返回 int Count() { int n = 0; n++; // ... return n; } int main() { int ret = Count(); cout << ret << endl; return 0; }
我们非常的熟悉,结果为 1
再来看一下下面这段代码👇
//传引用返回 int& Count() { int n = 0; n++; // ... return n; } int main() { int ret = Count(); //这里打印的结果可能是1,也可能是随机值 cout << ret << endl; return 0; }
🚨这里输出的结果有两种可能,一种为 1 ,另一种可能为随机值,原因是:传引用返回返回的是n的别名,但是这里存在的问题是我们返回n的别名也就是访问n这块空间,访问n这块空间就有两个结果:如果这个栈帧没有清除,它的值就是1,如果它的空间被清了,那么它的就会是一个 随机值
看图比较一下两段代码👇
上面的理解了之后,再看一段代码👇
int& Count() { int n = 0; n++; // ... return n; } int main() { int& ret = Count(); //这里打印的结果可能是1,也可能是随机值 cout << ret << endl; cout << ret << endl; //被覆盖 return 0; } • 17
这是为什么呢?
🚨函数调用要先传参,也就是先取值,这个时候还没有建立栈帧,取值之前还没有被覆盖,传参过去之后,建立栈帧,值不会受到影响;那第二次调用,再去取值,这时这个值已经被建立的栈帧覆盖了,所以输出的是随机值
理解了之后,再看一段代码👇
int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); Add(3, 4); cout << "Add(1, 2) is :" << ret << endl; return 0; }
这样的函数不能使用引用返回,是非常不安全的,相当于野引用(野指针)的方式
🚨注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回
🍿3、传值、传引用效率比较
🥗 传值和传引用的作为参数的性能比较
#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; }
🥗 传值和传引用的作为返回值的性能比较
#include <time.h> struct A { int a[10000]; }; A a; // 值返回 A TestFunc1() { return a; } // 引用返回 A& TestFunc2() { return a; } void TestReturnByRefOrValue() { // 以值作为函数的返回值类型 size_t begin1 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc1(); size_t end1 = clock(); // 以引用作为函数的返回值类型 size_t begin2 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc2(); size_t end2 = clock(); // 计算两个函数运算完成之后的时间 cout << "TestFunc1 time:" << end1 - begin1 << endl; cout << "TestFunc2 time:" << end2 - begin2 << endl; } int main() { TestReturnByRefOrValue(); return 0; }
🍿4、传引用返回修改返回对象
传引用返回还有一个隐藏作用:
假如一个顺序表要进行读和修改数据
struct SeqList { int* a; int size; int capacity; };
🚩C的接口设计:
//C的接口设计 //读取第i个位置的值 int SLAT(struct SeqList* ps, int i) { assert(i < ps->size); //... return ps->a[i]; } //修改第i个位置的值 void SLModify(struct SeqList* ps, int i, int x) { assert(i < ps->size); //... ps->a[i] = x; }
🚩CPP的接口设计:
//CPP的接口设计 //读 or 修改第i个位置的值 int& SLAT(struct SeqList& ps, int i) { assert(i < ps.size); //... return ps.a[i]; } int main() { struct SeqList s; //... SLAT(s, 0) = 10;//修改 SLAT(s, 1) = 20; SLAT(s, 2) = 30; cout << SLAT(s, 0) << endl;//打印 cout << SLAT(s, 1) << endl; cout << SLAT(s, 2) << endl; return 0; }
这里相比 C的接口设计来看,非常的香,减少了拷贝🥰
🍿5、总结
🚩传引用传参(任何时候都可以)
1、提高效率
2、输出型参数(形参的修改,影响实参)
🚩传引用返回(出了函数作用域对象还在才可以用)
1、提高效率
2、修改返回对象
🍪五、引用和指针的区别
🚩在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间
🚩在底层实现上实际是有空间的,因为引用是按照指针方式来实现的
int main() { int a = 0; int* p1 = &a; int& ref = a; return 0; }
我们来看下引用和指针的汇编代码对比:
🥰可以看到:引用和指针底层是一样的,可以说是引用就是化了妆的指针
引用和指针的不同点:
🚩1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
🚩2. 引用在定义时必须初始化,指针没有要求
🚩3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
🚩4. 没有NULL引用,但有NULL指针
🚩5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
🚩6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
🚩7. 有多级指针,但是没有多级引用
🚩8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
🚩9. 引用比指针使用起来相对更安全
😍这期内容有一点难理解,希望烙铁们能理解消化,有所收获哦!
总结🥰
以上就是 【C++】引用 的全部内容啦🥳🥳🥳🥳
本文章所在【C++初阶】专栏,感兴趣的烙铁可以订阅本专栏哦🥳🥳🥳
前途很远,也很暗,但是不要怕,不怕的人面前才有路。💕💕💕
小的会继续学习,继续努力带来更好的作品😊😊😊
创作写文不易,还多请各位大佬uu们多多支持哦🥰🥰🥰