2026/3/20 19:40:27
网站建设
项目流程
跟网站开发公司签合同主要要点,网站开发要学什么,wh网站建设,手机网站模板欣赏一、左值和右值
C11中引用了右值引用和移动语义#xff0c;可以避免无谓的复制#xff0c;提高程序性能。
左值可以取地址#xff0c;位于等号左边。 右值无法取地址#xff0c;位于等号右边。
代码语言#xff1a;javascript
AI代码解释
int a10;
a可以通过取…一、左值和右值C11中引用了右值引用和移动语义可以避免无谓的复制提高程序性能。左值可以取地址位于等号左边。 右值无法取地址位于等号右边。代码语言javascriptAI代码解释int a10;a可以通过取地址位于等号左边所以a是左值。10位于等号右边且无法通过取地址所以10是右值。代码语言javascriptAI代码解释struct A{ A(int a0) { a_a; } int a_; }; A aA();同样的a可以通过 取地址位于等号左边所以a是左值。 A()是个临时值没法通过 取地址位于等号右边所以A()是个右值。可见有地址的变量就是左值没有地址的字面值、临时值就是右值。二、左值引用引用本质是别名可以通过引用修改变量的值传参时传引用可以避免拷贝。 定义能指向左值不能指向右值的就是左值引用。代码语言javascriptAI代码解释#include iostream int main(int argc, char **argv) { int a 10; int ref_a a; int ref_b 10; // 左值引用指向了右值会编译失败 return 0; }左值引用指向了右值时编译出现报错代码语言javascriptAI代码解释error: invalid initialization of non-const reference of type ‘int’ from an rvalue of type ‘int’引用是变量的别名由于右值没有地址没法被修改所以左值引用无法指向右值。但是const左值引用是可以指向右值的。代码语言javascriptAI代码解释const int ref_b 10;// 编译通过const左值引用不会修改指向值。因此可以指向右值这也是为什么要使用 const 作为函数参数的原因之一如 std::vector 的 push_back 。代码语言javascriptAI代码解释void push_back (const value_type val);如果没有 const vec.push_back(5) 这样的代码就无法编译通过。三、右值引用再看下右值引用右值引用的标志是 顾名思义右值引用专门为右值而生可以指向右值不能指向左值。 右值引用的用途可以修改右值。代码语言javascriptAI代码解释#include iostream int main(int argc, char **argv) { int ref_a_right 10;//编译通过 int a 5; int ref_a_left a;// 编译不过右值引用不可以指向左值 ref_a_right 16; // 右值引用的用途可以修改右值 return 0; }右值引用指向左值时编译报错代码语言javascriptAI代码解释error: cannot bind ‘int’ lvalue to ‘int’四、左右值引用的本质引用的本质就是指向目标地址来获得资源。4.1、右值引用指向左值的办法通过std::move()可以将右值引用指向左值。代码语言javascriptAI代码解释#include iostream using namespace std; int main(int argc, char **argv) { int a 5;// a是左值 int ref_a_left a; // 左值引用 int ref_a_right std::move(a);//右值引用指向左值编译通过 cout a a endl; cout ref_a_left ref_a_left endl; cout ref_a_right ref_a_right endl; return 0; }在上边的代码里看上去是左值a通过std::move移动到了右值ref_a_right中那是不是a里边就没有值了并不是打印出a的值仍然是5。 执行结果代码语言javascriptAI代码解释a5 ref_a_left5 ref_a_right5std::move()是一个非常有迷惑性的函数 1不理解左右值概念的往往以为它能把一个变量里的内容移动到另一个变量 2事实上std::move()移动不了什么唯一的功能是把左值强制转化为右值让右值引用可以指向左值。其实现等同于一个类型转换 static_castT(lvalue) 。 所以单纯的std::move(xxx)不会有性能提升。同样的右值引用能指向右值本质上也是把右值提升为一个左值并定义一个右值引用通过std::move指向该左值。代码语言javascriptAI代码解释#include iostream using namespace std; int main(int argc, char **argv) { int a 5; int ref_a_left a; int ref_a_right std::move(a);//编译通过 ref_a_right 7; cout a a \nref_a_left ref_a_left \nref_a_right ref_a_right endl; cout a a endl; cout ref_a_left ref_a_left endl; cout ref_a_right ref_a_right endl; return 0; }此时a等于多少呢 运行结果如下地址没有变值改变了。代码语言javascriptAI代码解释a0x7ffd7bd6ea54 ref_a_left0x7ffd7bd6ea54 ref_a_right0x7ffd7bd6ea54 a7 ref_a_left7 ref_a_right74.2、左值引用、右值引用本身是左值还是右值被声明出来的左、右值引用都是左值。 因为被声明出的左右值引用是有地址的也位于等号左边。看下边代码代码语言javascriptAI代码解释// 形参是个右值引用 void change(int right_value) { right_value 8; } int main() { int a 5; // a是个左值 int ref_a_left a; // ref_a_left是个左值引用 int ref_a_right std::move(a); // ref_a_right是个右值引用 //change(a); // 编译不过a是左值change参数要求右值 //change(ref_a_left); // 编译不过左值引用ref_a_left本身也是个左值 //change(ref_a_right); // 编译不过右值引用ref_a_right本身也是个左值 change(std::move(a)); // 编译通过 change(std::move(ref_a_right)); // 编译通过 change(std::move(ref_a_left)); // 编译通过 change(5); // 当然可以直接接右值编译通过 cout a ; cout ref_a_left ; cout ref_a_right endl; // 打印这三个左值的地址都是一样的 }std::move会返回一个右值引用 int 它是左值还是右值呢 从表达式 int ref std::move(a) 来看右值引用 ref 指向的必须是右值所以move返回的 int 是个右值。 所以右值引用既可能是左值又可能是右值吗 确实如此右值引用既可以是左值也可以是右值如果有名称则为左值否则是右值。 或者说作为函数返回值的 是右值直接声明出来的 是左值。 这同样也符合前面章节对左值右值的判定方式其实引用和普通变量是一样的 int ref std::move(a) 和 int a 5 没有什么区别等号左边就是左值右边就是右值。从上述分析中得到如下结论从性能上讲左右值引用没有区别传参使用左右值引用都可以避免拷贝。右值引用可以直接指向右值也可以通过std::move指向左值而左值引用只能指向左值(const左值引用也能指向右值)。作为函数形参时右值引用更灵活。虽然const左值引用也可以做到左右值都接受但它无法修改有一定局限性。代码语言javascriptAI代码解释void f(const int n) { n 1; // 编译失败const左值引用不能修改指向变量 } void f2(int n) { n 1; // ok } int main() { f(5); f2(5); }五、右值引用和std::move使用场景std::move 只是类型转换工具不会对性能有好处右值引用在作为函数形参时更具灵活性。5.1、右值引用优化性能避免深拷贝1浅拷贝重复释放。 对于含有堆内存的类我们需要提供深拷贝的拷贝构造函数如果使用默认构造函数会导致堆内存的重复删除比如下面的代码代码语言javascriptAI代码解释#include iostream using namespace std; class A { public: A() :m_ptr(new int(0)) { cout constructor A endl; } ~A() { cout destructor A, m_ptr: m_ptr endl; delete m_ptr; m_ptr nullptr; } private: int *m_ptr; }; // 为了避免返回值优化此函数故意这样写 A Get(bool flag) { A a; A b; cout ready return endl; if (flag) return a; return b; } int main(int argc, char **argv) { { A a Get(false); } cout main finish endl; return 0; }运行报错代码语言javascriptAI代码解释constructor A constructor A ready return destructor A, m_ptr:0xe4b2a0 destructor A, m_ptr:0xe4ae70 destructor A, m_ptr:0xe4b2a0 free(): double free detected in tcache 2 已放弃 (核心已转储)2深拷贝构造函数。 在上面的代码中默认构造函数是浅拷贝main函数的 a 和Get函数的 b 会指向同一个指针 m_ptr在析构的时候会导致重复删除该指针。正确的做法是提供深拷贝的拷贝构造函数比如下面的代码代码语言javascriptAI代码解释#include iostream using namespace std; class A { public: A() :m_ptr(new int(0)) { cout constructor A endl; } A(const A a) :m_ptr(new int(*a.m_ptr)) { cout copy constructor A endl; } ~A() { cout destructor A, m_ptr: m_ptr endl; delete m_ptr; m_ptr nullptr; } private: int *m_ptr; }; // 为了避免返回值优化此函数故意这样写 A Get(bool flag) { A a; A b; cout ready return endl; if (flag) return a; return b; } int main(int argc, char **argv) { { A a Get(false); } cout main finish endl; return 0; }执行结果代码语言javascriptAI代码解释constructor A constructor A ready return copy constructor A destructor A, m_ptr:0x184e2a0 destructor A, m_ptr:0x184de70 destructor A, m_ptr:0x184e2c0 main finish3移动构造函数。 这样深拷贝构造函数就可以保证拷贝构造时的安全性但有时这种拷贝构造却是不必要的比如上面代码中的拷贝构造就是不必要的。上面代码中的 Get 函数会返回临时变量然后通过这个临时变量拷贝构造了一个新的对象 b临时变量在拷贝构造完成之后就销毁了如果堆内存很大那么这个拷贝构造的代价会很大带来了额外的性能损耗。有没有办法避免临时对象的拷贝构造呢看下面的代码代码语言javascriptAI代码解释#include iostream using namespace std; class A { public: A() :m_ptr(new int(0)) { cout constructor A endl; } A(const A a) :m_ptr(new int(*a.m_ptr)) { cout copy constructor A endl; } // 移动构造函数可以浅拷贝 A(A a):m_ptr(a.m_ptr) { // 为防止a析构时delete data提前置空其m_ptr a.m_ptr nullptr; cout move constructor A endl; } ~A() { cout destructor A, m_ptr: m_ptr endl; delete m_ptr; m_ptr nullptr; } private: int *m_ptr; }; // 为了避免返回值优化此函数故意这样写 A Get(bool flag) { A a; A b; cout ready return endl; if (flag) return a; return b; } int main(int argc, char **argv) { { A a Get(false); } cout main finish endl; return 0; }上面的代码中没有了拷贝构造取而代之的是移动构造 Move Construct。从移动构造函数的实现中可以看到它的参数是一个右值引用类型的参数 A这里没有深拷贝只有浅拷贝这样就避免了对临时对象的深拷贝提高了性能。这里的 A 用来根据参数是左值还是右值来建立分支如果是临时值则会选择移动构造函数。移动构造函数只是将临时对象的资源做了浅拷贝不需要对其进行深拷贝从而避免了额外的拷贝提高性能。这也就是所谓的移动语义 move 语义右值引用的一个重要目的是用来支持移动语义的。执行结果代码语言javascriptAI代码解释constructor A constructor A ready return move constructor A destructor A, m_ptr:0 destructor A, m_ptr:0x1eb9e70 destructor A, m_ptr:0x1eba2a0 main finish移动语义可以将资源堆、系统对象等通过浅拷贝方式从一个对象转移到另一个对象这样能够减少不必要的临时对象的创建、拷贝以及销毁可以大幅度提高 C 应用程序的性能消除临时对象的维护创建和销毁对性能的影响。5.2、移动move语义move是将对象的状态或者所有权从一个对象转移到另一个对象只是转义没有内存拷贝。要move语义起作用核心在于需要对应类型的构造函数支持。代码语言javascriptAI代码解释#include iostream #include vector #include cstdio #include cstdlib #include string.h using namespace std; class MyString { public: MyString() { m_data NULL; m_len 0; } MyString(const char *p) { m_len strlen(p); copy_data(p); } MyString(const MyString str) { m_len str.m_len; copy_data(str.m_data); std::cout Copy Constructor is called! source: str.m_data std::endl; } MyString operator(const MyString str) { if (this ! str) { m_len str.m_len; copy_data(str.m_data); } std::cout Copy Assignment is called! source: str.m_data std::endl; return *this; } // 用c11的右值引用来定义这两个函数 MyString(MyString str) { std::cout Move Constructor is called! source: str.m_data std::endl; m_len str.m_len; m_data str.m_data; //避免了不必要的拷贝 str.m_len 0; str.m_data NULL; } MyString operator(MyString str) { std::cout Move Assignment is called! source: str.m_data std::endl; if (this ! str) { m_len str.m_len; m_data str.m_data; //避免了不必要的拷贝 str.m_len 0; str.m_data NULL; } return *this; } virtual ~MyString() { if (m_data) delete m_data; } private: char * m_data; size_t m_len; void copy_data(const char *s) { m_data new char[m_len 1]; memcpy(m_data, s, m_len); m_data[m_len] \0; } }; int main() { MyString a; a MyString(Hello);// Move Assignment MyString b a;// Copy Constructor MyString c std::move(a);// Move Constructor is called! 将左值转为右值 std::vectorMyString vec; vec.push_back(MyString(World)); // Move Constructor is called! return 0; }执行结果代码语言javascriptAI代码解释Move Assignment is called! source: Hello Copy Constructor is called! source: Hello Move Constructor is called! source: Hello Move Constructor is called! source: World有了右值引用和转移语义我们在设计和实现类时对于需要动态申请大量资源的类应该设计右值引用的拷贝构造函数和赋值函数以提高应用程序的效率。5.3、forward 完美转发forward 完美转发实现了参数在传递过程中保持其值属性的功能即若是左值则传递之后仍然是左值若是右值则传递之后仍然是右值。现存在一个函数。代码语言javascriptAI代码解释Templateclass T void func(T val);根据前面所描述的这种引用类型既可以对左值引用亦可以对右值引用。但要注意引用以后这个val值它本质上是一个左值 看下面例子代码语言javascriptAI代码解释int a 10; int b a; //错误a是一个右值引用但其本身a也有内存名字所以a本身是一个左值再用右值引用引用a这是不对的。因此有了std::forward()完美转发这种T val中的val是左值但如果用std::forward (val)就会按照参数原来的类型转发。代码语言javascriptAI代码解释int a 10; int b std::forwardint(a);示例代码语言javascriptAI代码解释#include iostream using namespace std; template class T void Print(T t) { cout L t endl; } template class T void Print(T t) { cout R t endl; } template class T void func(T t) { Print(t); Print(std::move(t)); Print(std::forwardT(t)); } int main() { cout -- func(1) endl; func(1); int x 10; int y 20; cout -- func(x) endl; func(x); // x本身是左值 cout -- func(std::forwardint(y)) endl; func(std::forwardint(y)); cout -- func(std::forwardint(y)) endl; func(std::forwardint(y)); return 0; }执行结果代码语言javascriptAI代码解释-- func(1) L1 R1 R1 -- func(x) L10 R10 L10 -- func(std::forwardint(y)) L20 R20 R20 -- func(std::forwardint(y)) L20 R20 L20分析 func(1)由于1是右值所以未定的引用类型Tv被一个右值初始化后变成了一个右值引用但是在func()函数体内部调用PrintT(v) 时v又变成了一个左值因为在std::forward里它已经变成了一个具名的变量所以它是一个左值因此示例测试结果第一个PrintT被调用打印出“L1调用PrintTstd::forward(v)时由于std::forward会按参数原来的类型转发因此它还是一个右值这里已经发生了类型推导所以这里的T不是一个未定的引用类型会调用void PrintTTt函 数打印 “R1”.调用PrintT(std::move(v))是将v变成一个右值v本身也是右值因此它将输出”R1func(x)未定的引用类型Tv被一个左值初始化后变成了一个左值引用因此在调用PrintT(std::forward(v))时它会被转发到void PrintTTt。5.4、综合示例代码语言javascriptAI代码解释#include stdio.h #include iostream #include cstring #include vector using namespace std; class A { public: int *m_ptr NULL; // 增加初始化 int m_nSize 0; A() :m_ptr(NULL), m_nSize(0) {} A(int *ptr, int nSize) { m_nSize nSize; m_ptr new int[nSize]; printf(A(int *ptr, int nSize) m_ptr:%p\n, m_ptr); if (m_ptr) { memcpy(m_ptr, ptr, sizeof(sizeof(int) * nSize)); } } A(const Aother)// 拷贝构造函数实现深拷贝 { m_nSize other.m_nSize; if (other.m_ptr) { printf(A(const A other) m_ptr:%p\n, m_ptr); if (m_ptr) delete[] m_ptr; printf(delete[] m_ptr\n); m_ptr new int[m_nSize]; memcpy(m_ptr, other.m_ptr, sizeof(sizeof(int) * m_nSize)); } else { if (m_ptr) delete[] m_ptr; m_ptr NULL; } cout A(const int i) endl; } // 右值应用构造函数 A(A other) { m_ptr NULL; m_nSize other.m_nSize; if (other.m_ptr) { m_ptr move(other.m_ptr); other.m_ptr NULL; } } ~A() { if (m_ptr) { delete[] m_ptr; m_ptr NULL; } } void deleteptr() { if (m_ptr) { delete[] m_ptr; m_ptr NULL; } } }; int main() { int arr[] { 1, 2, 3 }; A a(arr, sizeof(arr) / sizeof(arr[0])); cout m_ptr in a Addr: 0x a.m_ptr endl; A b(a); cout m_ptr in b Addr: 0x b.m_ptr endl; b.deleteptr(); A c(std::forwardA(a)); // 完美转换 cout m_ptr in c Addr: 0x c.m_ptr endl; c.deleteptr(); vectorint vect{1, 2, 3, 4, 5}; cout before move vect size: vect.size() endl; vectorint vect1 move(vect); cout after move vect size: vect.size() endl; cout new vect1 size: vect1.size() endl; return 0; }执行结果代码语言javascriptAI代码解释A(int *ptr, int nSize) m_ptr:0x219ce70 m_ptr in a Addr: 0x0x219ce70 A(const A other) m_ptr:(nil) delete[] m_ptr A(const int i) m_ptr in b Addr: 0x0x219d2a0 m_ptr in c Addr: 0x0x219ce70 before move vect size: 5 after move vect size: 0 new vect1 size: 55.5、emplace_back 减少内存拷贝和移动对于STLC11后引入了emplace_back接口。 emplace_back是就地构造不用构造后再次复制到容器中。因此效率更高。www.dongchedi.com/article/7599372466490016280www.dongchedi.com/article/7599372650099999257www.dongchedi.com/article/7599372851132776984www.dongchedi.com/article/7599372306620039742www.dongchedi.com/article/7599372306619908670www.dongchedi.com/article/7599370698540368409www.dongchedi.com/article/7599370149677842969www.dongchedi.com/article/7599370761858023961www.dongchedi.com/article/7599370557410378302www.dongchedi.com/article/7599369686161244697www.dongchedi.com/article/7599370201301500441www.dongchedi.com/article/7599370761857761817www.dongchedi.com/article/7599371529427960344www.dongchedi.com/article/7599369580343001625www.dongchedi.com/article/7599335913373680190www.dongchedi.com/article/7599334077720150590www.dongchedi.com/article/7599334457882984984www.dongchedi.com/article/7599335431117259326www.dongchedi.com/article/7599334113941701182www.dongchedi.com/article/7599333889857307198www.dongchedi.com/article/7599332892518580761www.dongchedi.com/article/7599334507862786584www.dongchedi.com/article/7599332232163131928www.dongchedi.com/article/7599332409993544254www.dongchedi.com/article/7599331431303103000www.dongchedi.com/article/7599331421261939224www.dongchedi.com/article/7599331283516801561www.dongchedi.com/article/7599330823195591193www.dongchedi.com/article/7599236014061912601www.dongchedi.com/article/7599236952864326168www.dongchedi.com/article/7599233838816461337www.dongchedi.com/article/7599234108371354174www.dongchedi.com/article/7599232673466499646www.dongchedi.com/article/7599231381528871449www.dongchedi.com/article/7599230852278911550www.dongchedi.com/article/7599230129096933950www.dongchedi.com/article/7599224698819592766www.dongchedi.com/article/7599377947404861977