【C++】-- 引用(二)

简介: 【C++】-- 引用

四、使用场景


1.做参数

引用可以做参数,可以代替指针的作用

如Swap函数,指针的写法如下,有多处使用"*"解引用

1. //指针写法
2. void Swap(int* left, int* right)
3. {
4.  int temp = *left;
5.  *left = *right;
6.  *right = *left;
7. }

如果用引用做参数,不需要解引用,不需要使用"*"

1. //引用写法
2. void Swap(int& left, int& right)
3. {
4.  int temp = left;
5.  left = right;
6.  right = left;
7. }


2.做返回值

先了解传值返回,再了解传引用返回


(1)传值返回

传值返回,返回的是返回对象c的拷贝,不拿c做返回值,c只是Add的临时变量,Add函数调用结束后,c就不在了,因此不能返回c,返回的是临时变量。

1. #include<iostream>
2. using namespace std;
3. 
4. int Add(int a, int b)
5. {
6.  int c = a + b;
7.  return c;
8. }
9. 
10. int main()
11. {
12.   int ret = Add(1, 2);
13. 
14.   return 0;
15. }

如何证明产生了这个临时变量?如下,使用ret接收返回值

1. #include<iostream>
2. using namespace std;
3. 
4. int Add(int a, int b)
5. {
6.  int c = a + b;
7.  return c;
8. }
9. 
10. int main()
11. {
12.   int& ret = Add(1, 2);
13. 
14.   return 0;
15. }

但编译不通过

根据常引用声明,这是权限被放大导致的编译错误。c没有使用const进行修饰,ret也没有使用const进行修饰,为什么会导致权限放大呢?这是由于中间产生了临时变量,临时变量具有常性,不可修改。

所以ret也应该被const修饰,传引用返回的c的引用,即临时变量的名称,也是c的别名,把c的别名给了ret。

1. #include<iostream>
2. using namespace std;
3. 
4. int Add(int a, int b)
5. {
6.  int c = a + b;
7.  return c;
8. }
9. 
10. int main()
11. {
12.   cosnt int& ret = Add(1, 2);//用const修饰引用
13. 
14.   return 0;
15. }

传值返回生成的临时对象是需要返回的对象的拷贝:

①如果需要返回的对象比较小,一般会使用寄存器存储这个临时对象

②如果这个对象比较大,这个临时对象通常会存在于上一个函数的栈帧中。假如c比较大,那么临时对象会存在main函数中。


(2)传引用返回

传引用返回,返回的是返回对象c的引用(别名),当调用完Add函数,return返回后,Add的栈帧被销毁了,但是它使用的内存空间还在,只是使用权不属于它了,所以再去访问那块内存空间时,内存空间有可能没被清空,也有可能被清空了,因此有可能是以前的值,也有可能是随机值,所以代码的运行结果是不确定的。

1. #include<iostream>
2. using namespace std;
3. 
4. int& Add(int a, int b)
5. {
6.  int c = a + b;
7.  return c;
8. }
9. 
10. int main()
11. {
12.   int& ret = Add(1, 2);
13.   Add(5, 7);
14. 
15.   cout << "ret = "<<ret << endl;
16. 
17.   return 0;
18. }

当调用Add(5,7) 时,调用的是同一个函数,在同一位置上再建立一个栈帧,栈帧大小是一样的,c的位置也一样,仅仅只是把c的位置的内容由3改成了12,因此ret的值也为12。

但如果调用Add(1,2) 后,下次调用的不是Add函数,是其他函数,那么c的位置的值会被改成其他内容。比如调用Add(1,2)后,再打印HelloWorld,那么会打印随机值。

1. #include<iostream>
2. using namespace std;
3. 
4. int& Add(int a, int b)
5. {
6.  int c = a + b;
7.  return c;
8. }
9. 
10. int main()
11. {
12.   int& ret = Add(1, 2);
13.   printf("Hello world\n");
14. 
15.   cout << "ret = "<<ret << endl;
16. 
17.   return 0;
18. }

因此实际中,如果出了函数作用域,返回对象就不存在了,不能用引用返回。如果非要用引用返回,就要使用static关键字,延长变量的生命周期,Add函数调用完毕,栈帧销毁,但是静态区不会销毁,栈帧销毁对函数局部变量没有影响。

1. #include<iostream>
2. using namespace std;
3. 
4. int& Add(int a, int b)
5. {
6.  static int c = a + b;//栈帧销毁对c没有影响
7.  return c;
8. }
9. 
10. int main()
11. {
12.   const int& ret = Add(1, 2);
13.   Add(5, 7);
14. 
15.   cout << "ret = " << ret << endl;
16. 
17.   return 0;
18. }

什么时候使用传值返回,什么时候使用传引用返回呢?

如果函数返回时,出了函数作用域,如果返回对象还在(还没还给操作系统) ,可以使用引用返回,如果已经还给系统了,必须要用传值返回。

传值传参和传值返回、传引用传参和传引用返回

TestFunc1传值,TestFunc2传引用

1. #include<iostream>
2. #include<time.h>
3. using namespace std;
4. 
5. struct A 
6. { 
7.  int a[10000]; 
8. };
9. 
10. void TestFunc1(A a)
11. {
12. }
13. 
14. void TestFunc2(A& a)
15. {
16. }
17. 
18. void TestRefAndValue()
19. {
20.   A aa;
21. 
22.   //以值作为函数参数
23.   size_t begin1 = clock();
24.   for (size_t i = 0; i < 10000; i++)
25.   {
26.     TestFunc1(aa);
27.   }
28.   size_t end1 = clock();
29. 
30.   //以引用作为函数参数
31.   size_t begin2 = clock();
32.   for (size_t i = 0; i < 10000; i++)
33.   {
34.     TestFunc2(aa);
35.   }
36.   size_t end2 = clock();
37. 
38.   //分别计算两个函数运行结束后的时间
39.   cout << "TestFun1(A)-time:" << end1 - begin1 << endl;
40.   cout << "TestFun2(A)-time:" << end2 - begin2 << endl;
41. 
42. }
43. int main()
44. {
45.   TestRefAndValue();
46.   return 0;
47. }

传值传参和传值返回:在传参和返回期间,形参是实参的一份拷贝,返回变量是变量本身的一份拷贝,实参和变量本身占多大空间,形参和返回变量就占多大空间,在函数内操作副本,对该变量的修改并不会修改函数外部的变量。

传引用传参:形参和实参是同一个东西,实际操作的就是该变量,由于引用是指向某个变量的,对引用的操作其实就是对他指向的变量的操作,因此函数内对变量进行修改的话,外部变量也会被相应修改。

如果不希望修改变量的值,需要选择传值而不是传引用。但传引用往往比传值效率要高。


五、引用和指针

1. int a = 10;
2. 
3. //引用:在语法上,这里给a这块空间取了一个别名,没有开辟新空间
4. int& ra = a;
5. ra = 20;
6. 
7. //指针:在语法上,这里定义了一个指针变量,开了4个字节空间,存储a的地址
8. int* pa = &a;
9. *pa = 20;

1. 引用是别名,指针是地址

2. 引用在定义时必须初始化,指针没有要求

3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体,指针可以被重新赋值以指向另一个不同的变量。

4. 没有NULL引用,但有NULL指针

5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占 4个字节)

6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

7. 有多级指针,但是没有多级引用

8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

9. 引用比指针使用起来相对更安全。       这是由于,从编译上看,程序在编译时分别将指针和引用添加到 符号表上,符号表上记录变量名、变量地址。指针变量在符号表上对应的地址值为指针变量的地址,而 引用在符号表上对应的地址值为 引用对象的地址值。符号表生成后就不会再更改,指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改(第(3)条)。因此引用比指针更安全。如:

1. int a= 10;
2. int b = 30;  
3. int*& rpa = pa;//rpa是pa即int*的别名,此时pa指向a
4. rpa = &b;//rpa作为pa的引用,rpa的值被修改了,因此pa的值也被修改了,现在rpa和pa的值都为b的地址
5. 
6. printf("&a = %p\n", &a);
7. printf("&b = %p\n", &b);
8. printf("rpa = %p\n", rpa);
9. printf("pa = %p\n", pa);

相关实践学习
基于阿里云短信服务的防机器人验证
基于阿里云相关产品和服务实现一个手机验证码登录的功能,防止机器人批量注册,服务端采用阿里云ECS服务器,程序语言选用JAVA,服务器软件选用Tomcat,应用服务采用阿里云短信服务,
相关文章
|
1月前
|
存储 编译器 C语言
初谈C++:引用-1
初谈C++:引用
47 0
|
8月前
|
算法 Java 程序员
【跨代引用】
【跨代引用】
|
8月前
|
存储 前端开发 rax
【C++】C++引用(上)
【C++】C++引用(上)
|
21天前
|
安全 测试技术 C++
C++中的引用
C++中的引用
10 1
|
2天前
引用
引用类型 默认null va的ava的va的ava的内存需要划分成为5个部分: 1.栈(Stack)存放的都是方法中的局部变量。方法的运行一定要在栈当中运行。 2.堆(Heap)凡是new出来的东西,都是在堆当中 堆内存的东西都有一个地址值:16进制 堆内存的数据,都有默认值。规则: 整数 默认是0 浮点 默认0.0 字符 默认'\u0000' 布尔 默认false 引用类型 默认null
7 0
|
1月前
|
设计模式 JavaScript 前端开发
不正确的引用 this
不正确的引用 this
12 0
|
6月前
|
安全 编译器 C++
C++引用详解
C++引用详解
49 0
|
10月前
|
编译器 C语言 C++
[C++: 引用】(一)
[C++: 引用】(一)
32 0
|
编译器 C++
C++之引用(上)
C++之引用(上)
60 0
|
存储 安全 编译器
C++之引用(下)
C++之引用(下)
67 0