白塔网站建设,设计本网站怎么样,湖南网站seo地址,怎么做加盟美容院网站目录 1#xff0c;C11的发展历史
2#xff0c;列表初始化
2.1C98传统的{}
2.2#xff0c;C11中的{}
2.3#xff0c;C11中的std::initializer_list
3#xff0c;右值引用和移动语义
3.1#xff0c;左值和右值
3.2#xff0c;左值引用和右值引用 3.3#xff0c;…目录 1C11的发展历史
2列表初始化
2.1C98传统的{}
2.2C11中的{}
2.3C11中的std::initializer_list
3右值引用和移动语义
3.1左值和右值
3.2左值引用和右值引用 3.3引用延长生命周期
3.4左值和右值的参数匹配 3.5移动语义
为什么需要移动语义
移动构造函数与移动赋值运算符
使用场景
常见问题与缺陷
代码示例自定义类的移动语义 3.6右值引用和移动语义在传参中的提效
3.7引用折叠
3.8完美转发 1C11的发展历史
C11是C的第二个主要版本并且是从C98起的最重要更新。C11是C编程语言的一个重要版本于2011年正式发布。它引入了许多新特性和改进极大地增强了 C 的功能和易用性。下面介绍它的一些主要特性
2列表初始化
2.1C98传统的{}
在C98中一般数组和结构体支持使用{}初始化。 struct point { int x; int y; }; int main() { int arr1[5] {1,2,3,4,5}; point p { 1,2 }; return 0; } 2.2C11中的{} 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; }; C11后想统一初始化的方式试图一切对象皆可使用{}初始化{}初始化也叫列表初始化。内置类型支持自定义类型也支持自定义类型本质是类型转换中间会产生临时对象经过编译器优化后变成直接构造。 //C11 //内置类型支持{}初始化 int x { 2 }; //自定义类型 //本质是{202511}构造出临时对象再拷贝给d1但是编译器优化为直接用{202511}构造d1 Date d1 { 2025,1,1 }; //这里的d2引用的是{202472}的临时对象 const Date d2 { 2024,7,2 }; //需要注意的是C98支持单参数时类型转换也可以不用加{} Date d3 { 2025 }; Date d4 2025; {}初始化可以省略 //可以省略掉 int x2{ 2 }; point p1{ 1,2 }; Date d6{ 2025,1,2 }; const Date d7{ 2024,8,15 }; C11的列表初始化在许多场景下会带来不少的便利如容器push/insert多参数构造的对象时用{}会很方便。 vectorDate v; //有名对象传参 v.push_back(d6); //匿名对象传参 v.push_back(Date(2025, 1, 2)); //比起有名对象和匿名对象{}初始化更有性价比 v.push_back({ 2025,1,2 }); 2.3C11中的std::initializer_list
上面的初始化已经很方便但是对于一个容器的初始化来说还是不太方便。比如一个vector对象我们想用N个值去初始化那么我们需要实现多个构造函数才能支持:vectorint v1{1,2,3}vectorint v2{1,2,3,4}vectorint v3{1,2,3,4,5};C11库中提供了一个std::initializer_list 这个类的本质是底层开一个数组将数据拷贝过来std::initializer_list 中有两个指针分别指向数组的开始和结束。这时只要我们的容器支持一个std::initializer_list的构造函数就可以支持多个值的{x1,x2,x3......}的初始化。STL中的 容器支持多个值构成的{x1,x2,x3,......}的初始化就是通过底层支持std::initiaalizer_list的构造实现的。如下图list和vector的构造函数中都增添了支持std::initializer_list的构造函数。 vectorint v1 { 1,2,3,4,5 }; vectorint v2 { 1,2,3,4,5,6 }; //这里pair对象的{}初始化和map的initializer_list构造结合到一起了 mapstring, string dict { {sort,排序 },{string,字符串} }; 3右值引用和移动语义
C98中就有引用的语法而C11中新增了右值引用的语法特性之前的引用叫做左值引用。无论左值引用还是右值引用都可以理解为是在给变量取别名。
3.1左值和右值
左值是一个表示数据的表达式如变量名或解引用 的指针一般是持久状态存储在内存中我们可以获取它的地址。左值可以出现在赋值符号的左边也可以是在右边。如果左值用const修饰就不能给它赋值但可以取它的地址。右值也是一个表示数据的表达式要么是常量或者是临时对象等右值可以出现在赋值符号的右边但不能出现在左边右值不能取地址。左值的英文简写为lvalue右值的英文简写为rvalue。传统认为它们分别是left value、right value 的缩写。现代C中lvalue被解释为loactor value的缩写可意为存储在内存中、有明确存储地址可以取地址的对象而rvalue被解释为read value指的是那些可以提供数据值但是不可以寻址例如临时变量常量存储于寄存器中的变量等也就是说左值和右值的核心区别就是能否取地址。 //左值可以取地址 //以下均为左值 int* p new int(0); int b 1; const int c b; *p 10; string s(1111111); s[0] x; double x 1.1, y 2.2; //右值不能取地址 //以下几个均为右值 10; x y; string(111111); 3.2左值引用和右值引用 Type r1xType r2y。其中第一个语句就是左值引用 本质是给左值取别名。同理第二个语句就是给右值引用本质是给右值取别名。左值引用不能直接引用 右值需加上const修饰。 //左值可以取地址 //以下均为左值 int* p new int(0); int b 1; const int c b; *p 10; string s(1111111); s[0] x; double x 1.1, y 2.2; //右值不能取地址 //以下几个均为右值 10; x y; string(111111); //左值引用给左值取别名 int r1 b; int* r2 p; int r3 *p; string r4 s; char r5 s[0]; //左值引用不能直接引用右值需加上cosnt const int rx1 10; const double rx2 x y; const string rx3 string(111111); 右值引用不能直接引用 左值但可以引用move(左值。 //左值可以取地址 //以下均为左值 int* p new int(0); int b 1; const int c b; *p 10; string s(1111111); s[0] x; double x 1.1, y 2.2; //右值不能取地址 //以下几个均为右值 10; x y; string(111111); //右值引用 int rr1 10; double rr2 x y; string rr3 string(111111); //右值引用不能直接引用 左值但可以引用 move(左值) int rrx1 move(b); int* rrx2 move(p); int rrx3 move(*p); string rrx4 (move)(s); string rrx5 (string)s; move是库里面的一个函数模板本质内部做了强制类型转换涉及到一些引用折叠的知识 需要注意的是变量表达式都是左值属性也就意味着一个右值被右值引用绑定后右值引用变量是一个左值。 //右值引用 int rr1 10; double rr2 x y; string rr3 string(111111); //这里要注意的是rr1的属性是左值要想被右值引用绑定除非move一下 int a move(rr1); 3.3引用延长生命周期
右值引用可用于为临时对象延长生命周期const的左值引用也能延长临时对象生存期但这些对象无法被修改。 string s1 Test; //s1s1生成临时对象 const string s2 s1 s1; //const左值引用延长生命周期 string s3 s1 s1; //右值引用延长生命周期 s3 Test; cout s3 endl; 3.4左值和右值的参数匹配
C98中在函数的形参部分我们会用const 修饰左值引用的方式这样实参在传递左值和右值时都可以匹配。C11后分别重载左值引用const左值引用和右值引用作为形参的f函数那么实参时左值会调用f(左值引用实参是const 左值引用时会调用f(const 左值引用实参是右值引用时会调用f(右值引用。 void f(int x) { cout f(int x) endl; } void f(const int x) { cout f(const int x) endl; } void f(int x) { cout f(int x) endl; } int main() { int x 1; const int y x; f(x);//调用f(int x) f(y);//调用f(const int x) f(3);//调用f(int x) f(move(x));//调用f(int x) //右值引用变量是左值属性的 int z 1; f(z); //调用f(int x) f(move(z));//调用f(int x) return 0; } 3.5移动语义
移动语义是现代编程语言如C11及更高版本、Rust中用于优化资源管理的重要机制。其核心目标是避免不必要的拷贝通过转移资源所有权而非复制提升程序性能尤其在处理动态内存、文件句柄等资源时效果显著。 为什么需要移动语义
传统拷贝的缺陷
对于包含动态内存或系统资源的对象如std::vector,std::string等拷贝构造函数会深度复制所有数据导致性能开销。
移动语义的优化
直接窃取临时对象如右值的资源避免复制。 移动构造函数与移动赋值运算符
移动构造函数接受右值引用参数转移资源。
class MyClass {
public:// 移动构造函数MyClass(MyClass other) noexcept : data_(other.data_) ,size_(other.size_) {other.data_ nullptr; // 置空原对象避免重复释放other.size_ 0;}
private:int* data_;size_t size_;
};
移动赋值运算符类似移动构造处理对象赋值。
MyClass operator(MyClass other) noexcept {if (this ! other) {delete[] data_; // 释放当前资源data_ other.data_; // 转移资源size_ other.size_;other.data_ nullptr;other.size_ 0;}return *this;
}
使用场景
1显示触发移动语义
使用std:move将左值转化为右值
std::vectorint a {1, 2, 3};
std::vectorint b std::move(a); // a变为空资源转移给b
注意被移动后的对象处于有效但未定义状态通常为空不可再使用其值。
2返回值优化
编译器自动优化函数返回的临时对象避免拷贝
std::vectorint createVector() {std::vectorint v {1, 2, 3};return v; // 编译器可能直接构造v到调用方无需移动或拷贝
}
3STL容器的移动支持
标准库容器如std::vector、std::string已实现移动语义
std::vectorstd::string vec;
std::string s data;
vec.push_back(std::move(s)); // 移动s到容器避免拷贝字符串内容
常见问题与缺陷
1误用std:move
对局部变量过早移动导致后续访问未定义
std::vectorint a {1, 2, 3};
std::vectorint b std::move(a);
std::cout a.size(); // 未定义行为a可能为空
2未实现移动语义的类 若类未定义移动操作编译器可能回退到拷贝即使使用std::move。 规则若用户定义了拷贝构造函数、拷贝赋值或析构函数编译器不会自动生成移动操作。
3异常安全
移动操作应标记为noexcept否则某些容器如std::vector可能仍选择拷贝。
代码示例自定义类的移动语义
#include iostream
#include utility // for std::moveclass Buffer {
public:Buffer(size_t size) : size_(size), data_(new int[size]) {}// 移动构造函数Buffer(Buffer other) noexcept : size_(other.size_), data_(other.data_) {other.size_ 0;other.data_ nullptr;}// 移动赋值运算符Buffer operator(Buffer other) noexcept {if (this ! other) {delete[] data_;data_ other.data_;size_ other.size_;other.data_ nullptr;other.size_ 0;}return *this;}~Buffer() { delete[] data_; }private:size_t size_;int* data_;
};int main() {Buffer a(100);Buffer b std::move(a); // 调用移动构造函数Buffer c(200);c std::move(b); // 调用移动赋值运算符return 0;
}
3.6右值引用和移动语义在传参中的提效
C11以后STL容器的push和insert接口增加了右值引用版本。
以vector容器的push_back为例 当实参是一个左值时继续调用拷贝构造进行拷贝当实参是一个右值时容器内部调用移动构造提高效率。
3.7引用折叠
C中不能直接定义引用的引用 如int r1i这样写会直接报错通过模板或typedef的类型操作可以构成引用的引用。通过模板或typedef中的类型操作构成引用的引用时这时C11给出了一个引用折叠的规则右值引用的右值引用折叠成右值引用所有其他组合均折叠成左值引用。 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 3; //r4的类型是int return 0; } Function(T t)函数模板程序中假设实参是int右值模板参数T推导出是int实参是int左值模板参数T推导出是int再结合引用折叠规则就实现了实参是左值实例化出左值引用版本形参的 Function实参是右值实例化出右值引用版本形参的Function。 //根据引用折叠规则f1实例化后总是一个左值引用 template class T void f1(T x) {} //根据引用折叠规则f2实例化后可以是一个左值引用也可以是一个右值引用 template class T void f2(T x) {} int main() { //折叠-实例化为 f1(int x) f1int(n); //f1(0) //报错 //折叠-实例化为 f1(int x) f1int(n); //f1int(0) //报错 //折叠-实例化为 f1(int x) f1int(n); //f1int(0); //报错 //折叠-实例化为 f1(const int x) f1const int(n); f1const int(0); //折叠-实例化为 f1(const int x) f1const int(n); f1const int(0); //没有折叠-实例化为 f2(int x) //f2int(n); //报错 f2int(0); //折叠-实例化为 f2(int x) f2int(n); //f2int(0); //报错 //折叠-实例化为 f2(int x) //f2int(n); //报错 f2int(0); return 0; } 像f2这样的函数模板中T x参数看起来是右值引用参数但是由于引用折叠的规则他传递左值时就是左值引用传递右值时就是右值引用有些地方也把这种函数模板的参数叫做万能引用。
3.8完美转发
Function(T t)函数模板程序中传左值实例化以后就是左值引用的Function函数传右值实例化以后就是右值引用的Function函数。但是我们在前面讲过一个变量表达式都是左值属性也就是一个右值被右值引用表达式绑定以后右值引用变量表达式是左值属性的。也就是说Function函数中的 t 是左值属性的。如果Function函数中调用一个func函数那么我们把t传给下一层函数func那么匹配的都是左值引用的func函数。这里想要保持t对象的原属性就需要使用完美转发实现。 std::forward用于在转发参数时保持其原始的类别 。完美转发forwrad本质是一个函数模板它主要还是通过引用折叠的方式实现。 void func(int x) { cout 左值引用 endl; } void func(const int x) { cout const 左值引用 endl; } void func(int x) { cout 右值引用 endl; } void func(const int x) { cout const 右值引用 endl; } template class T void Function(T t) { func(t); } int main() { Function(10); //10是右值 Function(int t) 右值 int a;、 Function(a); //a是左值 Function(int t) 左值 Function(move(a)); // Function(int t) 右值 const int b 8; Function(b); //Function(const int t) const左值 Function(move(b)); //Function(const int t) const 右值 return 0; } 上面的代码是没有使用 完美转发的场景运行结果如下 可以看出在将t传给下一层的func函数时匹配的都是左值引用。 经过完美转发后的代码和运行结果 template class T void Function(T t) { //func(t); func(forwardT(t)); }