无论左值引用还是右值引用都是引用
左值引用
就是c++最初的,定义时只用一个&符的引用。
右值引用
定义右值引用的格式如下:
类型 && 引用名 = 右值表达式;
特点:
1,左值引用也可以作为右值引用,但是右值引用不可以作为左值引用
2,可以给字面值常量建立右值引用,但是不可以给左值建立右值引用。
int n=10;
int &&num=n;//cuowu
int &&nm=100;
为什么需要右值引用
引入右值引用的主要目的是提高程序运行的效率。有些对象在复制时需要进行深复制(深拷贝),深复制往往非常耗时。合理使用右值引用可以避免没有必要的深复制操作。
右值引用的使用
不要纠结于右值引用的的定义,右值引用更重要的是对它的的使用。
移动构造函数
使用临时对象,将临时对象的内存拷贝到新对象中,再释放掉这个临时对象。看起来就像内存移动了一样,所以叫移动构造函数。
move
将左值转换为右值,用来进行内存移动。
本质上就是把原来的内存赋值给新对象,再释放掉原来对象的内存,看起来就像原来变量的内存从一个对象移动到另一个对象一样。
用来实现,左值或者右值的保持性质的转发。
返回类型是引用
1,返回的变量不是指针的肯定不行;
#include <iostream>
int& get(){
int i=1;
return i;
}
int main(){
int& a=get();
std::cout<<a<<std::endl;
return 0;
}
原因:就是引用绑定的对象,i的地址已经释放。
2,返回的对象是指针,为什么可以。
#include <iostream>
int& get(){
int *i=new int(1);
return *i;
}
int main(){
int& a=get();
std::cout<<a<<std::endl;
return 0;
}
因为引用本质就是指针。
建立引用就是开辟栈区的一块内存用于保存一个变量的地址或者一块堆区或者文件映射区的地址。
以上这种情况就属于引用绑定到一块堆区地址。
*i取值是1,但是1存储的地址在new开辟的内存,引用返回值对象的地址。
变量i本质上是地址的别名。
接收者是变量和引用
接收者是变量
#include <iostream>
int a{12};
int& get(){
return a;
}
int main(){
int b=get();
b=13;
std::cout<<b<<std::endl;
std::cout<<a<<std::endl;
return 0;
}
开辟栈区的内存存储值。
接收者是引用
#include <iostream>
int a{12};
int& get(){
return a;
}
int main(){
int& b=get();
b=13;
std::cout<<b<<std::endl;
std::cout<<a<<std::endl;
return 0;
}
开辟栈区的内存存储地址。
返回引用的本质
就是返回值的地址。
类型的初始值设定项时,限定符被丢弃
引用和绑定到常量对象问题
问题:
1,const修饰的成员方法不能返回引用;
2,引用不能绑定常量对象;
原理:
引用的使用是为了方便修改绑定对象的值,如果绑定的对象不可以修改,引用就没有意义了。
const修饰的成员方法:
类(c++中还包括结构体)的非静态函数都有一个type *const this的指针参数,这个指针指向的地址不能改变。
函数后加const :type* const this---->const type*const this
所以const修饰的函数说明类对象的成员不可以被改变,而返回引用本质上是因为想要改变成员的值,所以冲突。如果想要改变值就不要const,如果只是获取值,就不要返回引用。
引用的约束
1,引用绑定的对象的类型要和引用的类型一致,否则错误;
原因:编译时会出现临时中间变量,这个中间临时量会被绑定到普通引用,产生c++语法错误。
2,左值引用(普通引用)不能绑定常量,字面值,表达式。
原因:c++语法规定左值引用绑定的对象必须是值可以改变的非临时量。
c++对象本身(就是地址)的权限问题
涉及到指针和引用
因为不同变量之间复制只是值赋值,没有权限影响。
权限只能缩小,不能放大。