C++引用

目录

引用的概念和基本用法

左值引用

常引用

右值引用

传值、传引用效率比较

引用和指针的区别


引用的概念和基本用法

在C++中,引用是一种特殊的变量类型,它提供了一种简单而有效的方法来操作其他变量的值。引用是一个别名,它引用了另一个变量的地址。引用在定义时必须初始化,并且一旦初始化后就不能再引用其他变量。

引用的定义方式是在变量名前面加上&符号,例如:

int a = 10;
int &b = a;

这里的&符号表示b是a的引用,也就是b和a指向同一块内存地址,对b的操作会直接影响到a的值。

引用特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体

左值引用

左值引用(Lvalue reference)是最常见的引用类型,它是一种指向左值的引用,可以绑定到具有名称的对象,例如变量、数组元素、成员变量等。

左值引用的定义方式是在变量名前面加上&符号,例如:

int a = 10;
int& b = a;

这里的&符号表示b是a的左值引用,也就是b和a指向同一块内存地址,对b的操作会直接影响到a的值。

左值引用的主要用途有以下几个方面:

  1. 作为函数参数传递,可以避免复制大型对象而带来的性能开销。在函数中使用左值引用参数,可以直接修改实参的值,从而达到修改函数外部变量的目的。

  2. 在函数返回值中使用左值引用,可以使函数返回一个左值,从而可以继续对其进行操作。例如:

    int& func(int& a) {
        return a;
    }
    int b = 10;
    func(b) = 20; // b的值变为20
    

    这里的func函数返回b的左值引用,因此可以对其进行赋值操作。

需要注意的是,左值引用只能绑定到左值,不能绑定到右值。如果试图将左值引用绑定到右值,编译器会报错。此外,左值引用在使用时需要注意避免悬空引用的问题,即引用的对象已经被销毁而引用仍然存在的情况。

常引用

常引用(const reference)是一种特殊的引用类型,它可以绑定到一个常量对象或表达式的结果,并且不能修改所引用的对象的值。常引用的定义方式是在变量名前面加上const关键字和&符号,例如:

const int& a = 10;

这里的const关键字表示a是一个常量引用,不能通过a修改其所引用的对象的值。常引用主要用于函数参数传递和返回值类型,可以避免复制对象而带来的性能开销。

常引用的主要用途有以下几个方面:

  1. 作为函数参数传递,可以避免复制大型对象而带来的性能开销,并且可以保证函数不会修改实参的值。在函数中使用常引用参数,可以直接访问实参的值,但不能修改实参的值。

  2. 在函数返回值中使用常引用,可以避免复制大型对象而带来的性能开销,并且可以保证函数返回的对象不会被修改。在函数中返回常引用,可以直接返回函数外部变量的引用,但不能修改其值。

需要注意的是,常引用只能绑定到常量对象或表达式的结果,不能绑定到非常量对象。如果试图将常引用绑定到非常量对象,编译器会报错。此外,常引用在使用时需要注意避免悬空引用的问题,即引用的对象已经被销毁而引用仍然存在的情况。

右值引用

在C++11中,引入了右值引用(Rvalue reference)的概念。右值引用是一种特殊的引用类型,它主要用于移动语义和完美转发。

右值引用的定义方式是在变量名前面加上&&符号,例如:

int&& a = 10;

这里的&&符号表示a是一个右值引用,它可以绑定到一个临时对象或表达式的结果。与左值引用不同,右值引用不能绑定到左值,例如:

int b = 20;
int&& c = b; // 错误,不能将右值引用绑定到左值

右值引用的主要用途有以下几个方面:

  1. 移动语义:右值引用可以用于实现移动构造函数和移动赋值运算符,从而避免不必要的内存拷贝操作,提高程序的性能。

  2. 完美转发:右值引用可以用于实现完美转发,即在函数中将参数按原样转发给其他函数,从而避免不必要的拷贝操作,提高程序的性能。

需要注意的是,右值引用的生命周期通常很短,只在表达式求值期间存在,因此不能将其用于存储对象的地址或引用。此外,右值引用在使用时需要注意避免悬空引用的问题,即引用的对象已经被销毁而引用仍然存在的情况。

传值、传引用效率比较

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

以下是值和引用的作为返回值类型的性能比较,传参比较也是类似的

#include<iostream>
using namespace std;

#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;
}

可以看到,返回值和返回引用的效率差别在返回对象越大区别越明显

引用和指针的区别

在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

我们可以把以下代码反汇编看一下

int main()
{
 int a = 10;
 
 int& ra = a;
 ra = 20;
 
 int* pa = &a;
 *pa = 20;
 
 return 0;
}

以下是反汇编代码,


	int& ra = a;
006C201C  lea         eax,[a]  
006C201F  mov         dword ptr [ra],eax  
	ra = 20;
006C2022  mov         eax,dword ptr [ra]  
006C2025  mov         dword ptr [eax],14h  

	int* pa = &a;
006C202B  lea         eax,[a]  
006C202E  mov         dword ptr [pa],eax  
	*pa = 20;
006C2031  mov         eax,dword ptr [pa]  
006C2034  mov         dword ptr [eax],14h  

可以看到,引用底层还是开辟了一块空间来存储地址,使用引用来改值时,还是通过定义引用时存储的地址来对a的内存空间进行修改,和指针的实现方式类似。

引用和指针的不同点 :
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用 在定义时 必须初始化 ,指针没有要求
3. 引用 在初始化时引用一个实体后,就 不能再引用其他实体 ,而指针可以在任何时候指向任何一个同类型
实体
4. 没有 NULL 引用 ,但有 NULL 指针
5. sizeof 中含义不同 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占
4 个字节 )
6. 引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同, 指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值