左值是某种存储支持的变量,右值是临时变量或可读但没有固定寻址方法的变量。
左值有具体的地址可以访问,右值有地址但没有固定寻址方法,右值是一种优化技巧。
左值引用只能接受左值,若加上const修饰就可以接收右值,但实际上用const修饰以后左值引用的变量以后,引用得来的变量就变成了一个不可修改的右值。
右值分纯右值,和将亡值。纯右值是只字面值、算数表达式、返回非引用类型的函数调用等等的数据值;将亡值是c++11新引入的类型与右值引用(移动语义)相关的值类型。
用在函数传参和函数返回值时,左值引用避免了对象的拷贝
void get(int& a)//只接收左值,get(10)会报错,因为10是一个右值 void get(const int& a)//可以接收左值和右值,但是a无法再被修改
右值引用只能接受右值,语法上是再加上一个&,右值引用的好处是,当我们输入值为右值时,我们知道它只是一个临时创建的变量,我们可以放心对其进行修改。
右值引用实现了移动语义和完美转发
void get(int&& x)//右值引用,get(10)也不会报错 void get_string1(string&& s) { s = "修改"; } void get_string2(string&& s) { string* change; change = &s; *change = "硬改"; } int main() { string s1 = "不"; string s2 = "修改"; //get_string(s1) 报错,因为s1是一个左值 get_string1(s1+s2); cout << s1 + s2 <虽然右值无法直接访问地址,但是确实有地址的,可以通过间接的方式获取其内存地址
void get(int&& x,int*& i) { i = &x; cout << i << endl; } int main() { int* i = nullptr; get(10,i); cout << i << endl; cout << *i << endl; }右值引用可以用在move语义的状态,实现简单的move效果
class String { public: String() = default; String(const char* string) { std::cout << "create!" << std::endl; m_Size = strlen(string); m_Data = new char[m_Size]; memcpy(m_Data, string, m_Size); } String(const String& other) { std::cout << "copy!" << std::endl; m_Size = other.m_Size; m_Data = new char[m_Size]; memcpy(m_Data, other.m_Data, m_Size); } String(String&& other) noexcept { std::cout << "move!" << std::endl; m_Size = other.m_Size; m_Data = other.m_Data; other.m_Size = 0; other.m_Data = nullptr; } ~String() { std::cout << "delete!" << std::endl; delete m_Data; } void print() const { for (int i = 0; i < m_Size; i++) printf("%c", m_Data[i]); printf("n"); } private: char* m_Data; int m_Size; }; class A { public: A() = default; A(const String& s):s(s){} A(String&& s) :s((String&&)s) {} void print() { s.print(); } private: String s; };如果不使用右值引用的拷贝构造函数,在A a(“拷贝”)这样的对象实例化的时候,实际上先调用了有参构造函数,再调用了拷贝构造函数。
开辟了两个堆再依次销毁,浪费性能,那么我们可以使用右值引用实现一个简单的move效果(实际上做了一个浅拷贝的工作)。A(String&& s) :s(std::move(s)) {}//(String&&)很少用,一般直接用movemove()函数实际上就是将左值转为右值。
在不用右值引用时,函数模板不能完美地转发给内部调用函数,要么只能给内部的其他函数,被转发的参数只能是右值或左值其中之一,不能达到泛型的目的,使用右值引用后就能解决这个问题,达到完美转发。
完美转发实现函数时std::forward,std::forward可以保持原来的值属性不变。
#includevoid reference(int &v){ printf("左值n"); } void reference(int &&v){ printf("右值n"); } template void pass(T && v){ reference(v); reference(std::forward (v)); } std::forward内部实现:
template2.auto和decltype操作符constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type& __t) noexcept{ return static_cast<_Tp&&>(__t); } template constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type&& __t)noexcept{ static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument substituting _Tp is an lvalue reference type"); return static_cast<_Tp&&>(__t); } C++中auto是根据用户初始化内容自动推导类型
- 使用auto时必须初始化
- auto变量不能作为自定义类型的成员变量
- auto不能声明数组
- 模板实例化类型不能是auto类型
- auto作为函数返回值类型时,需要额外再次指定返回类型,如:auto get(int a,double b)->int
decltype可以用来获取变量、函数、表达式的类型
追踪返回值类型:auto和decltype相结合
auto func(int a,double b)->decltype(a+b) { return a+b; }在模板中使用更简洁
templateauto mul(T1 &t1,T2 &t2)->->decltype(t1*t2) { return t1*t2; }