C++ 引用
1. 左值引用
- 定义
引用即别名,某个变量的别名,对引用的操作就等同于对变量本身进行的操作,左值引用引用的对象只能是左值 - 语法形式
类型 & 引用名 = 变量;
int a = 100;
int &b = a;
b++;
cout<<b<<endl;//101
- 引用使用时注意事项
- 引用在定义时就必须同时进行初始化,初始化以后绑定的变量不能修改
- 引用的类型和初始haul的变量的类型要保持一致
2. 万能引用(常引用)
- 定义
引用定义时加上const修饰,该引用为常引用,不能通过常引用修改绑定的目标变量的值
const 类型& 引用名 = 变量;
类型 const& 引用名 = 变量
int a = 100;
const int& b = a;//b就是一个常
b++;//error
a++;//101
cout <<b<<endl;//101
- 左值和右值
普通引用也叫左值引用,只能引用左值;而常引用也叫万能引用,即可以引用左值,也可以引用右值
- 左值: 可以放在赋值操作的左侧,可以被修改
- 右值:可以放在赋值操作的右侧,不可以被修改,(临时变量都是右值,你可以被修改)
lvalue = rvalue
- Code
#include <iostream>
using namespace std;
int main (void){
//int &r1 = 100;//error普通引用只能引用左值
const int& r1 = 100;//ok
cout << r1 << endl;//100
int num =100;
//首先需要将num转换成char类型,转换结果保存到系统分配的临时变量中,r2实际引用的不是num本身,而是转换后的临时变量,而临时变量是右值
//char& r2 = num;
const char& r2 =num;//实际引用的是临时变量(右值)
cout << "&r2: "<<(void*)&r2<<endl;
cout << "&num: "<<(void*)&num<<endl;
//这时r2是临时变量的引用
return 0;
}
$ g++ constr.cpp -o const
$ ./const
100
&r2: 0xbf9d5c1b
&num: 0xbf9d5c1c
- 各个操作符与左值和右值的关系
/*查看各个操作符的返回值是左值还是右值*/
#include <iostream>
using namespace std;
int fun (void){
int num = 100;
return num ;
//int 临时变量=num; ->将亡右值
}
int main (void){
int a=1,b=2;
const int c = 3;
//原理:普通引用只能对应左值即左值引用
//因此可以被int & r接收的,就可以编译通过
//int& r = c ;//no
//int& r = a-b;//no
//int& r = a+b;//no
//int& r = (b+=a);//ok
//int& r = (a-=b);//ok
//int& r = a++;//no
//int& r = a--;//no
//int& r = --a;//ok
//int& r = ++a;//ok
//int& r = -a;//no
//int& r = +a;//no
//int& r = ~a;//no
//int& r = (fun());//no
return 0;
}
3. 右值引用
右值引用应该是C++11引入的一个非常重要的技术,因为它是移动语义(Move semantics)与完美转发(Perfect forwarding)的基石
- 移动语义:将内存的所有权从一个对象转移到另外一个对象,高效的移动用来替换效率低下的复制,对象的移动语义需要实现移动构造函数(move constructor)和移动赋值运算符(move assignment operator)。
- 完美转发:定义一个函数模板,该函数模板可以接收任意类型参数,然后将参数转发给其它目标函数,且保证目标函数接受的参数其类型与传递给模板函数的类型相同。
- 右值引用的定义
C++11以前,右值被认为是无用的资源,所以在C++11中引入了右值引用,就是为了重用右值。定义右值引用需要使用&&:
1> 语法形式
int && rrval = 右值;
- 右值引用一定不能被左值初始化,只能使用右值对其进行初始化
int x = 20;//左值
int && rrx1 = x;//
const int&& rrx2 = x;
2> 使用右值引用的目的
使用右值引用是为了延长用来初始化对象的声明周期,对于左值,其生命周期和其作用域有关,没有必要去延长
int x = 20;//左值
int&& rx = x * 2;//x*2的值是一个右值,使用rx延长其声明周期,当rx被一个右值初始化以后,其就变成了一个左值
int y = rx + 2;//因此可以对其重用:42
rx = 100; //一旦你初始化一个右值变量,该变量就程伟乐一个左值,可以被复制
当使用一个右值对象初始化一个右值引用变量以后,该右值变量就会变成左值,因此,该变量也就可以被赋值
3> 右值引用与函数参数
- Code1
// 接收左值
void fun(int& lref)
{
cout << "l-value reference\n";
}
// 接收右值
void fun(int&& rref)
{
cout << "r-value reference\n";
}
int main()
{
int x = 10;
fun(x); // output: l-value reference
fun(10); // output: r-value reference
}
通过上述代码可以看到函数重载的机制是区分左值引用与右值引用的,参数是左右引用还是右值引用是两个不同的函数重载版本
- Code2
void fun(const int& clref)
{
cout << "l-value const reference\n";
}
如果函数的参数为常引用,那么该函数既可以接收左值也可以接收右值(如果你没有提供右值引用的重载版本)
4. 引用型函数返回值
- 将引用用于函数的返回类型,这时函数的返回结果就是引用的实际变量的别名,可以避免返回值所带来的内存开销,提高代码的执行效率
- 如果函数返回类型是左值引用,那么该函数调用的表达式结果就也是一个左值,即可以对函数的表达式直接赋值
- 注意
可以从函数中返回成员变量,静态变量,全局变量的引用
不可以从函数中返回局部变量的引用,因为所引用的变量的内存会在函数返回以后被释放
5. 引用和指针
- 从C语言的角度看引用的实现,引用的本质就是指针,凡是C++中不推荐使用指针而是推荐使用引用
int i = 100;
int* const p = &i;
int& r = i;
*p <=等价=> r
- 指针定义时可以不作初始化,指针的指向的目标可以修改(指针常量除外);
而引用在定义时必须进行初始化,而且初始化以后,引用指向的目标不能在进行修改 - 可以定义指针的指针(二级指针),但是不可以定义引用的指针
- 可以定义指针的引用,但是不能定义引用的引用
- 可以定义函数的引用(函数指针类似)即函数的别名
6. 函数传参传递指针和引用的区别
- 指针
指针传递参数本质上是值传递,它所传递的是一个地址值.值传递过程中,被调用函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本,值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值(这里说的是实参指针本身的地址值不会变) - 引用
引用传递的过程中,被调函数的参数形式虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址(int &a的形式).被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址,访问主调函数中的实参变量,因此,被调函数对形参做的任何操作都影响主调函数中的实参变量
总结
指针和引用的相同点和不同点:
★相同点:
-
指针和引用都是地址的概念;
-
指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
★不同点:
-
指针是一个实体,而引用仅是个别名;
-
引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
-
引用没有
const
,指针有const
,const
的指针不可变;(具体指没有int& const a
这种形式,而const int& a
是有的, 前者指引用本身即别名不可以改变,这是当然的,所以不需要这种形式,后者指引用所指的值不可以改变) -
引用不能为空,指针可以为空;
-
sizeof 引用
得到的是所指向的变量(对象)的大小,而sizeof 指针
得到的是指针本身的大小; -
指针和引用的自增
++
运算意义不一样; -
引用是类型安全的,而指针不是 (引用比指针多了类型检查