当前位置:   article > 正文

C++----引用_c++函数引用

c++函数引用

1. 引用的概念

引用C++语言中引入的概念,引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和引用的变量共用同一块内存空间就像一个人既有正名,也会有小名或者外号~

  1. //引用的写法 引用变量类型& 引用变量名 = 引用实体
  2. int main
  3. {
  4. int a = 0;
  5. int& b = a;//b为a的引用
  6. return 0;
  7. }
  8. //引用的类型必须和引用的实体相同

 a,b共用一块内存。

2. 引用的特点

2.1 引用在定义时必须初始化

  1. int main
  2. {
  3. int a = 10;
  4. //int& b; 编译不通过
  5. int& b = a;
  6. }

2.2 一个变量可以有多个引用

  1. int main
  2. {
  3. int a = 10;
  4. int& b = a;
  5. int& c = a;
  6. int& d = c;
  7. }
  8. // b、c、d都是a的引用

2.3 引用只能引用一个实体,引用后不能再引用其他实体

  1. int main
  2. {
  3. int a = 10;
  4. int& b = a;
  5. int c = 5;
  6. //b = c 编译不通过
  7. }

3. 常引用

常引用就是在定义引用变量时,在前面加上一个const,和C语言中的const作用一样,将变量可读可写的权限变成只能可读的权限。相当于是权利缩小。

  1. int main
  2. {
  3. //引用的权利可以和实体相同,或者是缩小,但是不能放大。
  4. const int a = 10;
  5. //int& b = a; 实体权力为只读,引用权力放大,所以不正确;
  6. const int& b = a;
  7. //int& c = 10; 引用可以直接引用整数,但是整数具有常属性,所以权力应该是只读的。
  8. const int& c = 10;
  9. double d = 2.2;
  10. //int& e = d; 低精度类型引用实体时,会发生截断,在给e赋值时,中间会产生临时变量,d先赋值给
  11. // 临时变量,再给到e,而临时变量具有常属性,所以引用要用const修饰。
  12. const int& e = d;
  13. }

可以验证一下上面的第三个例子,e并不是d的引用: 

 在vs2019下,可以看到d和e的地址并不相同,就说明d和e并不使用同一块内存,所以d不是e的引用,间接证明,e赋值给d的时候是产生了临时变量,引用了临时变量。

常引用在函数传参时有时会起到一些作用,要注意引用的权限。 

4. 使用场景

4.1 引用做函数参数

交换数值在C语言中是用指针写的,用起来比较麻烦,在C++中可以用引用传参:

  1. //用指针写的交换数值函数,用解引用,比较麻烦
  2. void swap(int *a, int *b)
  3. {
  4. int tmp = *a;
  5. *a = *b;
  6. *b = tmp;
  7. }
  8. //用引用写的交换数值函数
  9. //引用是实体的别名,所以直接交换两个引用,就是交换两个实体,方便容易理解
  10. void swap(int& x, int& y)
  11. {
  12. int tmp = x;
  13. x = y;
  14. y = tmp;
  15. }

4.2 引用做函数返回值

4.2.1

 引用做函数返回值就是返回n的别名,不会产生新的临时变量。因为n由static修饰,所以n被储存在静态区,就算Count()函数执行完毕,n也不会被销毁。所以ret可以引用n的值。

如果不小心没加static,第一次打印ret也是可以打印出的,但是第二次打印就不能打印出1了。要解释这种现象就要从函数栈帧的角度看待这个问题。1.首先在第一次调用Count()函数时,n为1,这没问题,返回n的别名m(姑且称为m),但是问题是n是局部变量了,Count()函数调用完后n,m会随着它的栈帧一起销毁,为什么ret还能引用m呢?2. 因为函数栈帧只是通常意义上的“销毁”,里面的数据并没有马上销毁,它们仍然存在,只是操作系统不允许再访问了。而ret引用m,访问m是非法访问的!但是是可以访问的,(不允许但是可以,就像上中学时不允许学生谈恋爱,但是仍然有谈恋爱的。。)3. 输出函数也要调用函数栈帧,但是要先传参数,1先被传到了cout函数中,所以能被打印出来,但是这时Count()函数之前的栈帧已经被cout函数调用的栈帧覆盖了,原来n的位置也被覆盖了,所以在第二次调用cout时,ret不再是1了而是随机值!

 4.2.2

这个道理和上面的一样,第一次调用Add得到3,但由于栈帧内的数据仍然存在,所以ret能访问到c,ret引用c的引用d。第二次调用,函数栈帧仍然在原来的地方,不过c变成了11,所以引用d也是11,ret仍然是那一块内存,所以也是11。 

和4.21.相同,第一次可以打印出11,第二次由于刚刚调用了cout,所以属于c的那一块内存被覆盖了,c变成了随机值,因此打印出随机值。 

 为什么在这里可以打印出3,那是因为static修饰c只能被初始化一次!所以后面不管调用多少次答案都会是3。

 如果将c=a+b的代数式和定义c的语句分隔开,那么之后的每次每次都会执行a+b了。

由以上的例子可知: 如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已 经还给系统了,则必须使用传值返回。

4.3 传值传引用,返回值返回引用的效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。传值和引用在作为传参以及返回值类型上效率相差很大

5. 引用和指针的区别

  • 在语法层面来讲引用就是一个别名,没有独立空间,和其引用实体共用同一块空间;
  • 在底层实现方面来讲,引用是有空间的,并且实现方式和指针相同。

汇编语言: 

 虽然牌名不一样,但都是一个厂家生产的~

引用和指针的一些不同之处:

  1. 引用 在定义时 必须初始化,指针没有要求
2. 引用 在初始化时引用一个实体后,就 不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
3. 没有 NULL 引用 ,但有 NULL指针
4. 在 sizeof 中含义不同 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占 4个字节 )
6. 有多级指针,但是没有多级引用
7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全  
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码诗人/article/detail/63158
推荐阅读
相关标签
  

闽ICP备14008679号