前言
引用就是别名(alias)。所谓别名,就是对已存在的对象另起一个名字。本身含义并不难理解,但与其它概念一组合,就成了使用难点。再加上新标准提出了新的一种引用-右值引用,引用这一概念就变得更加难以理解和使用。
左值、右值
在C++11中所有的值必属于左值、右值两者之一,右值又可以细分为纯右值、将亡值。在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。举个例子,int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。
右值、将亡值
在理解C++11的右值前,先看看C++98中右值的概念:C++98中右值是纯右值,纯右值指的是临时变量值、不跟对象关联的字面量值。临时变量指的是非引用返回的函数返回值、表达式等,例如函数int func()的返回值,表达式a+b;不跟对象关联的字面量值,例如true,2,”C”等。
C++11对C++98中的右值进行了扩充。在C++11中右值又分为纯右值(prvalue,Pure Rvalue)和将亡值(xvalue,eXpiring Value)。其中纯右值的概念等同于我们在C++98标准中右值的概念,指的是临时变量和不跟对象关联的字面量值;将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。
将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。
左值引用、右值引用
左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。
右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。
左值引用通常也不能绑定到右值,但常量左值引用是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。
int &a = 2; # 左值引用绑定到右值,编译失败
int b = 2; # 非常量左值
const int &c = b; # 常量左值引用绑定到非常量左值,编译通过
const int d = 2; # 常量左值
const int &e = c; # 常量左值引用绑定到常量左值,编译通过
const int &b =2; # 常量左值引用绑定到右值,编程通过
右值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值,例如:
int a;
int &&r1 = c; // 编译失败
int &&r2 = std::move(a); // 编译通过
正文
随着新标准(新标准往往就是新的技术)的提出,引用这一概念分成两类:左值引用、右值引用。其中左值引用是最常用的一种,而右值引用则是语言使用上的一种革新。
1.左值引用
左值引用的基本语法
Type &引用名 = 左值表达式;
#include
using namespace std;
int main()
{
int a = 10;
//ra是a的引用(别名),相当于把ra与a绑定。
int &ra(a);
cout << ra << " " << (void*)&ra << " " << (void*)&a << endl;
cin.get();
return 0;
}
运行
引用的基本规则
(1)声明引用的时候必须初始化,且一旦绑定,不可把引用绑定到其他对象。
(2)对引用的一切操作,就相当于对原对象的操作。
2.右值引用
以上是最常见的引用方式,我们称之为左值引用。为了顺利区分左右值引用的概念,我们先来看下左右值的概念。首先,左右值是表达式的属性。何为表达式?表达式由一个或多个运算对象组成。字面值和变量是最简单的表达式。一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址。
int a = 1;
a + 1; //a + 1 是表达式
在C++旧标准中,我们可以引用a,但却无法引用a+1,这是为什么呢?
这是因为,变量a被创建后,在它的作用域内是一直存在的。如此一来,它的引用是有存在意义的。而a+1不是一个对象,在运算中临时存在于寄存器中,而寄存器中的值是时刻被刷新的,创建a+1的引用显得没有任何意义。为了成功的使用类似于a+1这种右值的引用。C++新标准提出了区别于以往的引用-右值引用。
右值引用的基本语法
Type &&引用名 = 右值表达式;
#include
using namespace std;
int main()
{
int a = 10;
//右值引用
int &&ra(a+1);
cout << ra << ends << (void*)&ra << ends << (void*)&a << endl;
ra++;
cout << ra << endl;
cin.get();
return 0;
}
运行
对右值引用的一种可能的内部实现解释
int a = 1;
int *p = new int(a + 1);
int &ra = *p;
delete p; //不再使用引用了,动态内存的释放由C++自动管理
其中第二、三两句就对应右值引用:int &&ra(a+1);
3.引用作为函数参数
引用作为函数参数时,有着指针一样的功能。
#include
using namespace std;
void fun(int &ra)
{
cout << ra << ends << (void*)&ra << endl;
ra++;
}
int main()
{
int a = 10;
fun(a);
cout << a << ends << (void*)&a << endl;
cin.get();
return 0;
}
运行
在函数fun内,对形参ra的值,做出了更改,这一操作影响了实参a。显然引用的使用比指针简单。
4.引用与数组
int a[]{1, 2, 3, 4, 5};
int (&ra)[5] = a; //对数组进行引用
5.引用与指针
引用一级指针
int a(0), *p = &a;
int *&rp = p;
引用二级指针
int a(0), *p = &a;
int **pp = &p;
int **&rp = pp; //引用二级指针,若编译不通过,使用 int (**(&rp)) = pp;
网友评论