当前位置: 首页 > news >正文

便捷网站建设价格试用体验网站

便捷网站建设价格,试用体验网站,个人能进行网站开发,河南住房城乡建设部网站目录 一.列表初始化 1.1 C98传统的{} 1.2C11中的{} 1.3C中的std::initializer_list 二.右值引用和移动语义 2.1左值和右值 2.2左值引用和右值引用 2.3引用延长生命周期 2.4左值和右值的参数匹配 2.5右值引用和移动语义的使用场景 2.5.1左值引用主要使用场景 2.5.2移…目录 一.列表初始化 1.1 C98传统的{} 1.2C11中的{} 1.3C中的std::initializer_list 二.右值引用和移动语义 2.1左值和右值 2.2左值引用和右值引用 2.3引用延长生命周期 2.4左值和右值的参数匹配 2.5右值引用和移动语义的使用场景 2.5.1左值引用主要使用场景 2.5.2移动构造和移动赋值 2.5.3右值引用和移动语义解决传值返回问题 2.5.4右值引用和移动语义在传参中的提效 2.6类型分类  2.7引用折叠 2.8完美转发 一.列表初始化 1.1 C98传统的{} 在C98中用{}可以给数组、结构体进行初始化 比如 struct Point {int _x;int _y; }; int main() {int array1[] { 1, 2, 3, 4, 5 };int array2[5] { 0 };Point p { 1, 2 };return 0; } 1.2C11中的{} • C11以后想统一初始化方式试图实现一切对象皆可用{}初始化{}初始化也叫做列表初始化。  • 内置类型支持自定义类型也支持自定义类型本质是类型转换中间会产生临时对象最后优化了以后变成直接构造。 • {}初始化的过程中可以省略掉 • C11列表初始化的本意是想实现一个大统一的初始化方式其次他在有些场景下带来的不少便 利如容器push/inset多参数构造的对象时{}初始化会很方便。 #includeiostream #includevector using namespace std; struct Point {int _x;int _y; }; class Date { public:Date(int year 1, int month 1, int day 1):_year(year), _month(month), _day(day){cout Date(int year, int month, int day) endl;}Date(const Date d):_year(d._year), _month(d._month), _day(d._day){cout Date(const Date d) endl;} private:int _year;int _month;int _day; }; // 一切皆可用列表初始化且可以不加 int main() {// C98支持的 int a1[] { 1, 2, 3, 4, 5 };int a2[5] { 0 };Point p { 1, 2 };// C11支持的 // 内置类型支持int x1 { 2 };// 自定义类型支持 // 这里本质是用{ 2025, 1, 1 }构造一个Date临时对象 // 临时对象再去拷贝构造d1编译器优化后合二为一变成{ 2025, 1, 1}直接构造初始化d1// 运行一下我们可以验证上面的理论发现是没调用拷贝构造的只有一个构造Date d1 { 2025, 1, 1 };// 这里d2引用的是{ 2024, 7, 25 }构造的临时对象 const Date d2 { 2024, 7, 25 };// 需要注意的是C98支持单参数时类型转换也可以不用{} Date d3 { 2025 };Date d4 2025;// 可以省略掉 Point p1{ 1, 2 };int x2{ 2 };Date d6{ 2024, 7, 25 };const Date d7{ 2024, 7, 25 };// 不支持只有{}初始化才能省略 // Date d8 2025;vectorDate v;v.push_back(d1);v.push_back(Date(2025, 1, 1));// 比起有名对象和匿名对象传参这里{}更有性价比 v.push_back({ 2025, 1, 1 });return 0; } 需要额外提的一点是在创建的vector数组v中第一个push_back只需要一次拷贝构造函数但是后面的两个push_back一个是需要两个一个是需要三个这是因为vector数组需要扩容。 1.3C中的std::initializer_list • 上面的初始化已经很方便但是对象容器初始化还是不太方便比如一个vector对象我想用N个 值去构造初始化那么我们得实现很多个构造函数才能支持 vectorint v1 {1,2,3};vectorint v2 {1,2,3,4,5}; • C11库中提出了一个std::initializer_list的类 auto il { 10, 20, 30 }; // the type of il is an initializer_list 这个类的本质是底层开一个数组将数据拷贝过来std::initializer_list内部有两个指针分别指向数组的开始和结束。 • 这是他的文档initializer_liststd::initializer_list支持迭代器遍历。 • 容器支持一个std::initializer_list的构造函数也就支持任意多个值构成的 {x1,x2,x3...} 进行初始化。STL中的容器支持任意多个值构成的 {x1,x2,x3...} 进行初始化就是通过 std::initializer_list的构造函数支持的。 templateclass T class vector { public:typedef T* iterator;vector(initializer_listT l){for (auto e : l)push_back(e)} private:iterator _start nullptr;iterator _finish nullptr;iterator _endofstorage nullptr; }; 容器的赋值也支持initializer_list的版本 vector operator (initializer_listvalue_type il); map operator (initializer_listvalue_type il);#includeiostream #includevector #includemap using namespace std;int main() {initializer_listint mylist;mylist { 10, 20, 30 };cout sizeof(mylist) endl;// 这里begin和end返回的值initializer_list对象中存的两个指针 // 这两个指针的值跟i的地址跟接近说明数组存在栈上 int i 0;cout mylist.begin() endl;cout mylist.end() endl;cout i endl;// {}列表中可以有任意多个值 // 这两个写法语义上还是有差别的第一个v1是直接构造 // 第二个v2是构造临时对象临时对象拷⻉v2优化为直接构造 vectorint v1({ 1,2,3,4,5 });vectorint v2 { 1,2,3,4,5 };const vectorint v3 { 1,2,3,4,5 };// 这里是pair对象的{}初始化和map的initializer_list构造结合到一起用了 mapstring, string dict { {sort, 排序}, {string, 字符串} };// initializer_list版本的赋值支持 v1 { 10,20,30,40,50 };return 0; } 二.右值引用和移动语义 2.1左值和右值 • 左值是一个表示数据的表达式(如变量名或解引用的指针)一般是有持久状态存储在内存中我们可以获取它的地址左值可以出现赋值符号的左边也可以出现在赋值符号右边。定义时const 修饰符后的左值不能给他赋值但是可以取它的地址。 • 右值也是一个表示数据的表达式要么是字面值常量、要么是表达式求值过程中创建的临时对象 等右值可以出现在赋值符号的右边但是不能出现出现在赋值符号的左边右值不能取地址。 • 值得一提的是左值的英文简写为lvalue右值的英文简写为rvalue。传统认为它们分别是left value、right value的缩写。现代C中lvalue被解释为loactor value的缩写可意为存储在内 存中、有明确存储地址可以取地址的对象而rvalue被解释为read value指的是那些可以提供数据值但是不可以寻址例如临时变量字面量常量存储于寄存器中的变量等也就是说左值和右值的核心区别就是能否取地址。 #includeiostream using namespace std; int main() {// 左值可以取地址 // 以下的p、b、c、*p、s、s[0]就是常⻅的左值 int* p new int(0);int b 1;const int c b;*p 10;string s(111111);s[0] x;cout c endl;cout (void*)s[0] endl;// 右值不能取地址 double x 1.1, y 2.2;// 以下几个10、x y、fmin(x, y)、string(11111)都是常⻅的右值 10;x y;fmin(x, y);string(11111);//cout 10 endl;//cout (xy) endl;//cout (fmin(x, y)) endl;//cout string(11111) endl;return 0; } 2.2左值引用和右值引用 • Type r1 x; Type rr1 y; 第一个语句就是左值引用左值引用就是给左值取别 名第二个就是右值引用同样的道理右值引用就是给右值取别名。 • 左值引用不能直接引用右值但是const左值引用可以引用右值 • 右值引用不能直接引用左值但是右值引用可以引用move(左值) • template typename remove_reference::type move (T arg); • move是库里面的一个函数模板本质内部是进行强制类型转换当然他还涉及一些引用折叠的知 识。 • 需要注意的是变量表达式都是左值属性也就意味着一个右值被右值引用绑定后右值引用变量变量表达式的属性是左值 • 语法层面看左值引用和右值引用都是取别名不开空间。从汇编底层的角度看下面代码中r1和rr1 汇编层实现底层都是用指针实现的没什么区别。底层汇编等实现和上层语法表达的意义有时是背离的所以不要然到一起去理解互相佐证这样反而是陷入迷途。 int main() {// 左值可以取地址 // 以下的p、b、c、*p、s、s[0]就是常⻅的左值 int* p new int(0);int b 1;const int c b;*p 10;string s(111111);s[0] x;double x 1.1, y 2.2;// 左值引用给左值取别名 int r1 b;int* r2 p;int r3 *p;string r4 s;char r5 s[0];// 右值引用给右值取别名 int rr1 10;double rr2 x y;double rr3 fmin(x, y);string rr4 string(11111);// 左值引用不能直接引用右值但是const左值引用可以引用右值 const int rx1 10;const double rx2 x y;const double rx3 fmin(x, y);const string rx4 string(11111);// 右值引用不能直接引用左值但是右值引用可以引用move(左值) int rrx1 move(b);int* rrx2 move(p);int rrx3 move(*p);string rrx4 move(s);string rrx5 (string)s;//move其实就是强制转换// b、r1、rr1都是变量表达式都是左值 cout b endl;cout r1 endl;cout rr1 endl;// 这里要注意的是rr1的属性是左值所以不能再被右值引用绑定除非move一下 int r6 rr1;// int rrx6 rr1;int rrx6 move(rr1);return 0; } 这里有一个点需要单独提一下rr1是右值引用的右值但它的属性是左值而且再去用右值引用去引用它还要用move。 2.3引用延长生命周期 右值引用可用于为临时对象延长生命周期const的左值引用也能延长临时对象生存期但这些对象无法被修改。 int main() {std::string s1 Test;// std::string r1 s1; // 错误不能绑定到左值 const std::string r2 s1 s1; // OK到 const 的左值引用延⻓生存期 // r2 Test; // 错误不能通过到 const 的引用修改 std::string r3 s1 s1; // OK右值引用延⻓生存期 r3 Test; // OK能通过到非 const 的引用修改 std::cout r3 \n;return 0; } 2.4左值和右值的参数匹配 • C98中我们实现一个const左值引用作为参数的函数那么实参传递左值和右值都可以匹配。 • C11以后分别重载左值引用、const左值引用、右值引用作为形参的f函数那么实参是左值会匹配f(左值引用)实参是const左值会匹配f(const 左值引用)实参是右值会匹配f(右值引用)。 • 右值引用变量在用于表达式时属性是左值。 这里代码可以自行测试 #includeiostream using namespace std; void f(int x) {std::cout 左值引用重载 f( x )\n; } void f(const int x) {std::cout 到 const 的左值引用重载 f( x )\n; } void f(int x) {std::cout 右值引用重载 f( x )\n; } int main() {int i 1;const int ci 2;f(i); // 调用 f(int) f(ci); // 调调用 f(const int) f(3); // 调调用 f(int)如果没有 f(int) 重载则会调用 f(const int) f(std::move(i)); // 调用 f(int) // 右值引用变量在用于表达式时是左值 int x 1;f(x); // 调用 f(int x)f(std::move(x)); // 调用 f(int x) return 0; } 主要需要注意的是右值引用变量时传参时的调用调用的是左值引用的那个函数。 2.5右值引用和移动语义的使用场景 2.5.1左值引用主要使用场景 左值引用主要使用场景是在函数中左值引用传参和左值引用传返回值时减少拷贝同时还可以修改实参和修改返回对象的价值。左值引用已经解决大多数场景的拷贝效率问题但是有些场景不能使用传左值引用返回如addStrings和generate函数C98中的解决方案只能是被迫使用输出型参数解决。 class Solution { public:// 传值返回需要拷⻉ string addStrings(string num1, string num2) {string str;int end1 num1.size() - 1, end2 num2.size() - 1;// 进位 int next 0;while (end1 0 || end2 0){int val1 end1 0 ? num1[end1--] - 0 : 0;int val2 end2 0 ? num2[end2--] - 0 : 0;int ret val1 val2 next;next ret / 10;ret ret % 10;str (0 ret);}if (next 1)str 1;reverse(str.begin(), str.end());return str;} }; class Solution { public:// 这里的传值返回拷⻉代价就太大了 vectorvectorint generate(int numRows) {vectorvectorint vv(numRows);for (int i 0; i numRows; i){vv[i].resize(i 1, 1);}for (int i 2; i numRows; i){for (int j 1; j i; j){vv[i][j] vv[i - 1][j] vv[i - 1][j - 1];}}return vv;} }; 2.5.2移动构造和移动赋值 • 移动构造函数是一种构造函数类似拷贝构造函数移动构造函数要求第一个参数是该类类型的引用但是不同的是要求这个参数是右值引用如果还有其他参数额外的参数必须有缺省值。 • 移动赋值是一个赋值运算符的重载他跟拷贝赋值构成函数重载类似拷贝赋值函数移动赋值函数要求第一个参数是该类类型的引用但是不同的是要求这个参数是右值引用。 • 对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类移动构造和移动赋值才有 意义因为移动构造和移动赋值的第一个参数都是右值引用的类型他的本质是要“窃取”引用的右值对象的资源而不是像拷贝构造和拷贝赋值那样去拷贝资源从提高效率。 这里我实现一下string类看一看移动构造和移动赋值的作用 #define _CRT_SECURE_NO_WARNINGS #includeiostream #includeassert.h #includestring.h #includealgorithm using namespace std; namespace lwz {class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str _size;}//默认构造string(const char* str ):_size(strlen(str)), _capacity(_size){cout string(char* str)-构造 endl;_str new char[_capacity 1];strcpy(_str, str);}//析构函数~string(){cout ~string() -- 析构 endl;delete[] _str;_str nullptr;}void swap(string s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷贝构造string(const string s):_str(nullptr){cout string(const string s) -- 拷⻉构造 endl;reserve(s._capacity);for (auto ch : s){push_back(ch);}}// 移动构造 string(string s){cout string(string s) -- 移动构造 endl;swap(s);}//拷贝赋值string operator(const string s){cout string operator(const string s) -- 拷⻉赋值 endl;if (this ! s){_str[0] \0;_size 0;reserve(s._capacity);for (auto ch : s){push_back(ch);}}return *this;}// 移动赋值 string operator(string s){cout string operator(string s) -- 移动赋值 endl;swap(s);return *this;}void push_back(char ch){if (_size _capacity){size_t newcapacity _capacity 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] ch;_size;_str[_size] \0;}//能用[]访问char operator[](size_t pos){assert(pos _size);return _str[pos];}//扩容void reserve(size_t n){if (n _capacity){char* tmp new char[n 1];if (_str){strcpy(tmp, _str);delete[] _str;}_str tmp;_capacity n;}}string operator(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}size_t size() const{return _size;}private:char* _str nullptr;size_t _size 0;size_t _capacity 0;}; } int main() {lwz::string s1(xxxxx);// 拷⻉构造 lwz::string s2 s1;// 构造移动构造优化后直接构造 lwz::string s3 lwz::string(yyyyy);// 移动构造 lwz::string s4 move(s1);cout ****************************** endl;return 0; } 最后输出 2.5.3右值引用和移动语义解决传值返回问题 先来看一段代码 namespace lwz {string addStrings(string num1, string num2){string str;int end1 num1.size() - 1, end2 num2.size() - 1;int next 0;while (end1 0 || end2 0){int val1 end1 0 ? num1[end1--] - 0 : 0;int val2 end2 0 ? num2[end2--] - 0 : 0;int ret val1 val2 next;next ret / 10;ret ret % 10;str (0 ret);}if (next 1)str 1;reverse(str.begin(), str.end());cout ****************************** endl;return str;} } // 场景1 int main() {lwz::string ret lwz::addStrings(11111, 2222);cout ret.c_str() endl;return 0; } 这种情况下打印出来的是什么 这里我用的机器是vs2022编译器进行了很恐怖的优化。 nums1 nums2 str nums2 nums1 c_str() str • 下图展示了vs2019 debug环境下编译器对拷贝的优化左边为不优化的情况下两次拷贝构造右 边为编译器优化的场景下连续步骤中的拷贝合二为一变为一次拷贝构造。 • 需要注意的是在vs2019的release和vs2022的debug和release下面代码优化为非常恐怖会直接将str对象的构造str拷贝构造临时对象临时对象拷贝构造ret对象合三为一变为直接构造。 变为直接构造。要理解这个优化要结合局部对象生命周期和栈帧的角度理解如下图所示。这种优化也就是我们上面打印出来的结果。 右值对象构造有拷贝构造也有移动构造的场景 跟上面的是一样的  右值对象赋值只有拷贝构造和拷贝赋值没有移动构造和移动赋值的场景 // 场景2 int main() {lwz::string ret;ret lwz::addStrings(11111, 2222);cout ret.c_str() endl;return 0; } • 下图左边展示了vs2019 debug和 g test.cpp -fno-elide-constructors 关闭优化环境下编译器的处理一次拷贝构造一次拷贝赋值。 • 需要注意的是在vs2019的release和vs2022的debug和release下面代码会进⼀步优化直接构造要返回的临时对象str本质是临时对象的引用底层角度用指针实现。运行结果的角度我们可以看到str的析构是在赋值以后说明str就是临时对象的别名。 打印出  这里就相当于省略了拷贝构造的那部分直接构造一个临时对象而str就是这个临时对象的别名是一个右值引用。  右值对象赋值既有拷贝构造和拷贝赋值也有移动构造和移动赋值的场景 所以这里可以用移动赋值而移动赋值的情形跟这个拷贝赋值差不多也是省略了拷贝的那一部分。 2.5.4右值引用和移动语义在传参中的提效 • 查看STL文档我们发现C11以后容器的push和insert系列的接口否增加的右值引用版本 • 当实参是一个左值时容器内部继续调用拷贝构造进行拷贝将对象拷贝到容器空间中的对象 • 当实参是一个右值容器内部则调用移动构造右值对象的资源到容器空间的对象上 • 比如之前的list支持右值引用参数版本的push_back和insert  • 其实这里还有一个emplace系列的接口但是这个涉及可变参数模板这个需要到后面再说。 简单在我们自己实现的list中看一下: namespace lwz {templateclass Tstruct ListNode{ListNodeT* _next;ListNodeT* _prev;T _data;ListNode(const T data T()):_next(nullptr), _prev(nullptr), _data(data){}ListNode(T data):_next(nullptr), _prev(nullptr), _data(move(data)){}};templateclass T, class Ref, class Ptrclass ListIterator{public:typedef ListNodeT Node;typedef ListIteratorT, Ref, Ptr Self;Node* _node;ListIterator(Node* node):_node(node){}Ref operator*(){return _node-_data;//引用返回当前位置的引用}Ptr operator-(){return _node-_val;//返回的是地址}Self operator()//这里只是前置{_node _node-_next;return *this;}bool operator!(const Self it){return _node ! it._node;}};templateclass Tclass list{public:typedef ListNodeT Node;typedef ListIteratorT,T,T* iterator;typedef ListIteratorT,const T,const T* const_iterator;iterator begin(){return iterator(_head-_next);}iterator end(){return iterator(_head);}void empty_init(){_head new Node();_head-_next _head;_head-_prev _head;}list(){empty_init();}void push_back(const T x){insert(end(), x);}void push_back(T x){insert(end(), move(x));//这里x的属性会变成左值所以要move一下}iterator insert(iterator pos, const T x){Node* cur pos._node;Node* newnode new Node(x);Node* prev cur-_prev;// prev newnode curprev-_next newnode;newnode-_prev prev;newnode-_next cur;cur-_prev newnode;return iterator(newnode);}iterator insert(iterator pos, T x){Node* cur pos._node;Node* newnode new Node(move(x));Node* prev cur-_prev;// prev newnode curprev-_next newnode;newnode-_prev prev;newnode-_next cur;cur-_prev newnode;return iterator(newnode);}private:Node* _head;}; } 运行这一段代码 int main() {lwz::listlwz::string lt;//哨兵位的头结点调用string的构造cout ************************* endl;lwz::string s1(111111111111111111111);lt.push_back(s1);//push_back调用insertinsert调用Node()Node的默认构造调用_data()调用string的默认构造cout ************************* endl;lt.push_back(lwz::string(22222222222222222222222222222));//匿名对象这里是移动构造//因为创建的是临时对象创建了Node后后面会跟一个析构cout ************************* endl;lt.push_back(3333333333333333333333333333);cout ************************* endl;lt.push_back(move(s1));cout ************************* endl;return 0; } 打印出 2.6类型分类  • C11以后进一步对类型进行了划分右值被划分纯右值(pure value简称prvalue)和将亡值 (expiring value简称xvalue)。 • 纯右值是指那些字面值常量或求值结果相当于字面值或是一个不具名的临时对象。如 42、 true、nullptr 或者类似 str.substr(1, 2)、str1 str2 传值返回函数调用或者整形 a、baab 等。纯右值和将亡值C11中提出的C11中的纯右值概念划分等价于 C98中的右值。 • 将亡值是指返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达如 move(x)、static_cast(x) • 泛左值(generalized value简称glvalue)泛左值包含将亡值和左值。 • 值类别值类别-cppreference.com和Value categories这两个关于值类型的中文和英文的官方文档有兴趣可以了解细节。 • 有名字就是glvalue有名字且不能被move就是lvalue有名字且可以被move就是 xvalu没有名字且可以被移动则是prvalue。 简单的说左值是lvaluemove(lvalue)xvalue。其他没有名字的就是prvalue.  2.7引用折叠 • C中不能直接定义引用的引用如 int r i; 这样写会直接报错通过模板或typedef 中的类型操作可以构成引用的引用。 • 通过模板或typedef中的类型操作可以构成引用的引用时这时C11给出了一个引用折叠的规则右值引用的右值引用折叠成右值引用所有其他组合均折叠成左值引用。 • 下面的程序中很好的展示了模板和typedef时构成引用的引用时的引用折叠规则。 • 像f2这样的函数模板中Tx参数看起来是右值引用参数但是由于引用折叠的规则他传递左值时就是左值引用传递右值时就是右值引用有些地方也把这种函数模板的参数叫做万能引用。 • Function(Tt)函数模板程序中假设实参是int右值模板参数T的推导int实参是int左值模 板参数T的推导int再结合引用折叠规则就实现了实参是左值实例化出左值引用版本形参的 Function实参是右值实例化出右值引用版本形参的Function。 // 由于引用折叠限定f1实例化以后总是一个左值引用 templateclass T void f1(T x) {} // 由于引用折叠限定f2实例化后可以是左值引用也可以是右值引用 templateclass T void f2(T x) {}int main() {typedef int lref;typedef int rref;int n 0;lref r1 n; // r1 的类型是 int lref r2 n; // r2 的类型是 int rref r3 n; // r3 的类型是 int rref r4 1; // r4 的类型是 int // 没有折叠-实例化为void f1(int x) f1int(n);f1int(0); // 报错 // 折叠-实例化为void f1(int x) f1int(n);f1int(0); // 报错 // 折叠-实例化为void f1(int x) f1int(n);f1int(0); // 报错 // 折叠-实例化为void f1(const int x) f1const int(n);f1const int(0);// 折叠-实例化为void f1(const int x) f1const int(n);f1const int(0);// 没有折叠-实例化为void f2(int x) f2int(n); // 报错 f2int(0);// 折叠-实例化为void f2(int x) f2int(n);f2int(0); // 报错 // 折叠-实例化为void f2(int x) f2int(n); // 报错 f2int(0);return 0; } 总的来说参数是左值引用一直都是左值引用参数是右值引用只有右值的参数才是右值引用。  templateclass T void Function(T t) {int a 0;T x a;//x;cout a endl;cout x endl endl; } int main() {// 10是右值推导出T为int模板实例化为void Function(int t) Function(10); // 右值 int a;// a是左值推导出T为int引用折叠模板实例化为void Function(int t) Function(a); // 左值 // std::move(a)是右值推导出T为int模板实例化为void Function(int t) Function(std::move(a)); // 右值 const int b 8;// a是左值推导出T为const int引用折叠模板实例化为void Function(const int t)// 所以Function内部会编译报错x不能 Function(b); // const 左值// std::move(b)右值推导出T为const int模板实例化为void Function(const int t)// 所以Function内部会编译报错x不能 Function(std::move(b)); // const 右值 return 0; } 如果传参的是左值那么会把T推导为int所以打印出来的就只是一个空间的地址。 如果传参的是右值那么会把T推导为int所以打印出来的就是两个个空间的地址。 2.8完美转发 • Function(T t)函数模板程序中传左值实例化以后是左值引用的Function函数传右值实例化 以后是右值引用的Function函数。 •变量表达式都是左值属性也就意味着一个右值被右值引用绑定后右值引用变量表达式的属性是左值也就是说Function函数中t的属性是左值那么我们把t传递给下一层函数Fun那么匹配的都是左值引用版本的Fun函数。这里我们想要保持t对象的属性 就需要使用完美转发实现。 • template T forward (typename remove_reference::type arg); • template T forward (typename remove_reference::type arg) • 完美转发forward本质是一个函数模板他主要还是通过引用折叠的方式实现下面示例中传递给 Function的实参是右值T被推导为int没有折叠forward内部t被强转为右值引用返回传递给 Function的实参是左值T被推导为int引用折叠为左值引用forward内部t被强转为左值引用返回。   template class _Ty _Ty forward(remove_reference_t_Ty _Arg) noexcept { // forward an lvalue as either an lvalue or an rvaluereturn static_cast_Ty(_Arg); } ​​​​​​​ void Fun(int x) { cout 左值引用 endl; } void Fun(const int x) { cout const 左值引用 endl; } void Fun(int x) { cout 右值引用 endl; } void Fun(const int x) { cout const 右值引用 endl; } templateclass T void Function(T t) {Fun(t);//Fun(forwardT(t)); } int main() {// 10是右值推导出T为int模板实例化为void Function(int t) Function(10); // 右值 int a;// a是左值推导出T为int引用折叠模板实例化为void Function(int t) Function(a); // 左值 // std::move(a)是右值推导出T为int模板实例化为void Function(int t) Function(std::move(a)); // 右值 const int b 8;// b是左值推导出T为const int引用折叠模板实例化为void Function(const int t)Function(b); // const 左值 // std::move(b)右值推导出T为const int模板实例化为void Function(const int t)Function(std::move(b)); // const 右值 return 0; } 这段代码如果不用完美转发打印出来是 如果用完美转发 这里就是正常的输出了。
http://www.dnsts.com.cn/news/263523.html

相关文章:

  • 网站网页的书签怎么做巴中住房和城乡建设局网站
  • 济南建网站麦积区建设局网站
  • 购物网站建设案例网页图片显示不出来
  • 网站发展历程wordpress国人编辑器
  • 自己做网站要会什么软件wordpress 标签下的文章
  • 东莞网站建设+旅游9uu最新域址永久
  • 龙岗网站建设szaowwordpress 自定义注册
  • 佛山用户网站建站设计师推荐
  • 有哪些商业网站荣成网站建设
  • 网站快速排名优化承德市人才信息网
  • 营销微网站建设公司lamp网站架构
  • 自己建网站能赚钱吗免费网站设计定制
  • 网站入门高碑店做网站的公司
  • 杭州网站优化企业做汽车团购网站有哪些
  • 淘宝上的网站建设可信苏州行业网站建设
  • 新手怎么做跨境电商西安seo引擎搜索优化
  • 网站备案完成后企业服务公司经营范围
  • 关联网站有那些济南网站建设认可搜点网络
  • wordpress炫酷站延吉市建设厅网站
  • 营销型网站设计价格wordpress网站被攻击
  • 宜宾网站建设热点事件舆情分析报告
  • 网站主机托管wordpress v2pai
  • 虚拟主机只能静态网站天津百度网站排名优化
  • 网站开发实用技术介绍公司做网站 手机 电脑
  • 网店运营推广网站好的网站推荐一个
  • 环保主题静态网站模板云南网站建设价格低
  • 网站跳转域名不变用别人代码搭建网站
  • 婚恋网站建设技巧有没有免费做物流推荐的网站
  • 手机编码制网站广告公司好听的名字
  • 官方网站开发招标须知上海vis设计