C++的引用

引言

C语言中我们在函数传参时会面临传值和传地址的选择,这对初学者来说是一件很头疼的事情,在不断的积累与学习中我们不难总结出修改变量值时传地址,使用变量值时传值。

 #include <stdio.h>
 ​
 void swap(int *a,int *b)
 {
     int temp = *a;
     *a = *b;
     *b = temp;
 }
 ​
 void func(char *ptr)
 {
     ptr++;
 }
 ​
 int main()
 {
     int a = 5;
     int b = 6;
 ​
     //传地址,函数生效
     swap(&a, &b);
     //什么时候传地址,什么时候传值?
     //当修改实参变量值时,传实参变量的地址;(传实参变量的地址,既可以使用又可以修改)
     //当只是用不修改实参变量的值时,传实参变量名;(传实参变量名,只能用不能改)
 ​
     printf("a = %d b = %d\n", a, b);
 ​
     char *ptr = "hello world";
 ​
     //传变量名函数无法生效
     func(ptr);
 ​
     printf("ptr = %s\n", ptr);
     return 0;
 }

输出结果:

a = 6 b = 5

ptr = hello world

C++在C语言的基础上提出了引用的定义,在一定程度上解决了传值和传地址的问题。

引用的介绍

引用的使用方法相较于C语言中的传值和传地址要方便许多

 #include <iostream>
 ​
 using namespace std;
 ​
 void swap(int &a, int &b)  //引用作为函数形参:传值和传地址问题
 {
     int temp = a;
     a = b;
     b = temp;
 }
 ​
 int main()
 {
     int a,b;
     a = 5;
     b = 6;
 ​
     swap(a, b);
     
     cout << "a = " << a << "b = " << b << endl;
     return 0;
 }

输出结果 a = 6 b = 5

同样功能的交换函数使用引用实现就不需要考虑传值还是传地址的问题了,直接引用即可。

//传值:无法修改实参
void swap_value(int a, int b) { int tmp = a; a = b; b = tmp; }

//传地址:需要手动解引用,容易出错
void swap_ptr(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; }

//传引用:直接操作实参,简洁安全
void swap_ref(int &a, int &b) { int tmp = a; a = b; b = tmp; }
  • 引用:解决的是函数指针传参和返回值问题

  • 引用就是给变量起别名,操作引用相当于在操作引用所绑定的变量

  • 注意事项:定义引用必须绑定变量;一旦绑定一个变量就不能再绑定其他变量

引用其实是占用内存空间的,但是编译器对此做了优化,不将这段空间展示给开发人员,不能操作这段空间

 #include <iostream>
 ​
 using namespace std;
 ​
 struct node
 {
     int n;
     int &rn;
 };
 ​
 int main()
 {
     cout << sizeof(struct node) << endl;
     return 0;
 }

输出结果:16

引用作为函数形参时函数内部可以对目标元素进行修改,如果我们想防止某个数据被修改可以使用const关键字修饰,将其转变为常引用

 #include <iostream>
 using namespace std;
 ​
 int getValue(const int &ref) // 常引用作为参数
 {
     return ref * 2;
 }
 ​
 int main()
 {
     int a = 10;
     const int &b = a; // 常引用,不能通过 b 修改 a 的值
 ​
     cout << "b = " << b << endl;
     cout << "getValue(b) = " << getValue(b) << endl;
 ​
     // b = 20; // 编译错误,常引用不能修改原值
     return 0;
 }

左值引用和右值引用

左值和右值

C++中有左值和右值的概念

  • 左值:可以被修改的值可以作为左值(可以取地址的值可以作为左值)

  • 右值:不可以被修改的值可以作为右值(不可以取地址的可以作为右值)

 int num = 5;    //num的值可以修改,因此num时左值
 int *p = &num;  //可以对*p赋值,因此*p是左值
 (&num)++;       //此语句会引发编译器报错,说明&num不能修改,因此&num是右值

我们在判断一个值是否为左值通常判断一下它能否自加,能自加的是左值,不能的是右值

左值引用和右值引用

C++11以后才开始有了左值引用和右值引用的概念

  • 左值引用:只能绑定左值

  • 右值引用:只能绑定右值

 #include <iostream>
 using namespace std;
 ​
 //左值引用函数
 void func1(int &l_num)
 {
     cout << l_num << endl;
 }
 ​
 //右值引用函数
 void func2(int &&r_num)
 {
     cout << r_num << endl;
 }
 ​
 int main()
 {
     int num = 5;
 ​
     int &l_num = num;
     int &&r_num = 5;
     r_num++;        //能自加说明r_num是左值
 ​
     func1(num);
     func1(l_num);
     func2(5);
 ​
     //func2(r_num);     //此句代码会引发报错,左值不能作为参数传递到右值引用函数
     func2(std::move(r_num)); //std::move将左值转为右值,允许临时接管资源
     return 0;
 }
类型标准形式可接受调用者典型应用场景
非成员左值引用函数void func(T&)非const左值对象修改对象状态
void func(const T&)const左值对象只读访问
非成员右值引用函数void func(T&&)右值(临时对象)资源转移/移动语

程序中main函数中的r_num变量从C语言的角度来看与右值引用函数的形参貌似是同一数据类型,但是直接传入却会导致报错,究其原因就是因为r_num是左值可以改变值的大小,因此不得传入右值函数中。

左值引用和右值引用的作用

普通的函数在实参指针的情况下操作变量时需要进行拷贝操作,待操作完成后再将修改后的值写回去,而左值引用函数则是直接寻址并操作变量,无需复制数据,这样可以在一定程度上提高程序的运行效率。

在面对体积较大的对象时,普通函数在进行值传递时任然采取的时拷贝的方式,开销较大,但右值引用函数则会通过移动语义直接接管右值资源(如堆内存地址),避免拷贝操作,仅修改了指针指向,性能提升显著。

左值引用实现对对象的安全高效访,右值函数实现零开销的资源转移,二者结合让C++能够同时保持高性能和抽象能力。

引用和指针的区别

指针和引用本质上的区别就是指针是一种数据类型,而引用不是,因为指针可以指向任何数据类型,但是没有指向引用的指针,由于引用在初始化时必须绑定对象,而且一旦绑定无法更换,但指针在操作过程中可能产生野指针造成程序崩溃,因此引用比指针要更安全。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐