网站后台搭建教程网页设计图片不显示
2026/4/9 2:54:08 网站建设 项目流程
网站后台搭建教程,网页设计图片不显示,网站建设设计方案,wordpress 最好的seo一. C11的发展历史 C11是C的第二个主要版本#xff0c;并且是从C98起的最重要更新。它引入了大量更改#xff0c;标准化了既有实践#xff0c;并改进了对C程序员可用的抽象。在它最终由ISO在2011年8月12日采纳前#xff0c;人们曾使用名称“C0X”#xff0c;因为它曾被期…一. C11的发展历史C11是C的第二个主要版本并且是从C98起的最重要更新。它引入了大量更改标准化了既有实践并改进了对C程序员可用的抽象。在它最终由ISO在2011年8月12日采纳前人们曾使用名称“C0X”因为它曾被期待在2010年之前发布。C03与C11期间花了8年时间故而是迄今为止最长的版本间隔。从那时起C有规律地每3年更新一次。二. 列表初始化万物皆可 {} 的统一初始化方案C98 中初始化方式杂乱数组用 {}、类用构造函数、内置类型直接赋值C11引入列表初始化统一初始化通过{}实现 “一切对象皆可初始化”兼顾简洁性与安全性。2.1 核心用法支持所有对象类型C11中的{}:内置类型支持自定义类型也支持自定义类型本质是类型转换中间会产生临时对象最后优化变成直接构造{}初始化的过程中可以省略掉C11列表初始化的本意是实现一个大统一的初始化方法其次他在有些场景下带来的不少便利如容器push多参数构造的对象时{}初始化会很方便。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; }; void Insert(const Date d) { } Date func() { //Date d(2025, 11, 15); //return d; //return { 2025,11,15 }; //Date d; //return d; return {}; } int main() { // C98 int array1[] { 1,2,3,4,5 }; int array2[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 }); Insert({ 2025,11,15 }); return 0; }2.2 C11中的std::initializer_liststd::initializer_list是 C11 新增的轻量级容器本质存储两个指针指向数组首尾数组存储初始化列表中的数据位于栈上。其核心接口包括begin()、end()和size()支持迭代器遍历。上面的初始化已经很方便但是对象是容器初始化还是不太方便比如一个vector对象我想用N个值去构造初始化那么我们得实现多个构造函数才能支持vectorint v1 {1,2,3}; vectorint v2 {1,2,3,4,5};C库中提出std::initializer_list这个类auto il { 10, 20, 30 }; // the type of il is an initializer_list 这个类的本质是底层开⼀个数组将数据拷⻉过来std::initializer_list内部有两个指针分别指向数组的开始和结束。std::initializer_list支持迭代器遍历容器支持一个std::initializer_list的构造函数也就支持任意多个值构成的{x1,x2,x3……}进行初始化。STL中的容器支持任意多个值构成的{x1,x2,x3……}进行初始化就是通过std::initializer_list的构造函数支持的。int main() { vectorint v1 { 1,2,3,4 }; vectorint v2 { 1,2,3,4,5,6,7,7 }; // 里面的括号是pair列表初始化外面的是initialize_list mapstring, string dict { {sort,排序},{string,字符串} }; v1 { 10,20,30 }; auto il { 10,20,30 }; cout typeid(il).name() endl; std::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; return 0; }三. 右值引用与移动语义彻底解决拷贝效率问题C98 中左值引用Type无法绑定右值大量临时对象的拷贝导致性能浪费。C11 新增右值引用Type和移动语义通过 “窃取” 右值对象的资源替代拷贝操作大幅提升效率。3.1 基础概念左值与右值左值是⼀个表示数据的表达式(如变量名或解引⽤的指针)⼀般是有持久状态存储在内存中我们可以获取它的地址左值可以出现赋值符号的左边也可以出现在赋值符号右边。定义时const修饰符后的左值不能给他赋值但是可以取它的地址。右值右值也是⼀个表⽰数据的表达式要么是字⾯值常量、要么是表达式求值过程中创建的临时对象等右值可以出现在赋值符号的右边但是不能出现出现在赋值符号的左边右值不能取地址。核心区别能否取地址是左值和右值的核心区别。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(11111111111111); s[0] x; cout p endl; cout b endl; cout c endl; cout (*p) endl; cout s 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; }3.2 右值引用语法右值引用专门绑定右值延长其生命周期左值引用非 const不能绑定右值但const左值引用可绑定右值但无法修改右值引用不能直接绑定左值但可通过std::move()强制转换左值为右值move本质是类型转换不移动数据。int main() { int* p new int(0); int b 1; const int c b; *p 10; string s(11111111111111); 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; // b、r1、rr1都是变量表达式都是左值 cout b endl; cout r1 endl; cout rr1 endl; // 这里要注意的是右值引用后rr1的属性是左值所以不能再被右值引用绑定除非move一下 // 后面还会再讲到的 int r6 r1; // int rrx6 rr1; int rrx6 move(rr1); return 0; }3.3 引用延长生命周期我们知道引用可以延长对象生命周期那么临时对象和匿名对象的生命周期可以通过右值引用来延长const的左值引用也行但是不能修改。class A { public: A() { cout A() endl; } ~A() { cout ~A() endl; } }; int main() { A aa1; // 延长匿名对象的生命周期 const A ref1 A();// const左值引用但是这样就不能修改了 A ref2 A();// 右值引用 cout main end() endl; return 0; }3.4 右值和左值的参数匹配C98中我们实现一个const左值引用作为参数的函数那么实参传递左值和右值都可以匹配C11之后分别重载左值引用const左值引用右值引用作为形参的f函数那么实参是左值会匹配f(左值引用)实参是const左值会匹配(const左值引用),实参是右值会匹配f(右值引用)。右值引用变量在用于表示式时属性是左值这个我们后面还会讲到的到时候大家就会对它有更直观的认知了。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; }3.5 移动语义核心移动构造与移动赋值先回顾一下左值引用的一些场景左值引⽤主要使⽤场景是在函数中左值引⽤传参和左值引⽤传返回值时减少拷⻉同时还可以修改实参和修改返回对象的价值。左值引⽤已经解决⼤多数场景的拷⻉效率问题但是有些场景不能使⽤传左值引⽤返回如addStringsgenerate函数C98中的解决⽅案只能是被迫使⽤输出型参数解决。那么C11以后这⾥可以使⽤右值引⽤做返回值解决吗显然是不可能的因为这⾥的本质是返回对象是⼀个局部对象函数结束这个对象就析构销毁了右值引⽤返回也⽆法改变对象已经析构销毁的事实。移动构造和移动赋值移动构造函数是一种构造函数类似拷贝构造函数移动构造函数要求第一个参数是该类类型的引用但是不同的是要求这个参数是右值引用如果还有其他参数额外的参数必须有缺省值。移动赋值是一个赋值运算符重载他跟拷贝赋值构成函数重载类似拷贝赋值函数移动赋值函数要求第一个参数是该类类型的引用但是不同的是要求这个参数是右值引用。对于像string/vector这样的深拷贝的类或者深拷贝的成员变量的类移动构造和移动赋值才有意义因为移动构造和移动赋值的第一个参数都是右值引用的类型他的本质是要“窃取”引用的右值对象的资源而不是像拷贝构造函数和拷贝赋值那样去拷贝资源提高效率。下面的Lotso::string样例实现了移动构造和移动赋值我们需要结合场景去理解。#define _CRT_SECURE_NO_WARNINGS 1 #includeiostream #includeassert.h #includestring.h #includealgorithm using namespace std; namespace Scy { 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); } void swap(string s) { ::swap(_str, s._str); ::swap(_size, s._size); ::swap(_capacity, s._capacity); } // 拷贝构造 string(const string s) { 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; } ~string() { //cout ~string() -- 析构 endl; delete[] _str; _str nullptr; } 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; } } 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; } 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; }; // 传值返回需要拷贝 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 str endl; return str; } } int main { bit::string s1(xxxxx); // 拷⻉构造 bit::string s2 s1; // 构造移动构造优化后直接构造 bit::string s3 bit::string(yyyyy); // 移动构造 bit::string s4 move(s1); cout ****************************** endl; return 0; }再补充两个场景// 场景一 int main() { Scy::string ret Scy::addStrings(11111, 2222); cout ret.c_str() endl; cout ret endl; } // 场景二 int main() { Scy::string ret; // …… ret Scy::addStrings(11111, 2222); cout ret.c_str() endl; cout ret endl; }3.5.1 右值对象构造只有拷贝构造没有移动构造的场景下图1中展现了 vs2019 debug 环境下编译器对拷贝的优化左边为不优化的情况下两次拷贝构造右边为编译器优化的场景下连续步骤中的拷贝合二为一变为一次拷贝构造。需要注意的是在 vs2019的release和 vs2022的debug和release下面代码优化十分恐怖会直接将strd对象的构造str拷贝构造临时对象临时对象拷贝构造ret对象合三为一变为直接构造要理解这个优化要结合局部对象⽣命周期和栈帧的⻆度理解如图三。linux下可以将下⾯代码拷⻉到test.cpp⽂件编译时⽤g test.cpp -fno-elide-constructors的⽅式关闭构造优化运⾏结果可以看到图1左边没有优化的两次拷⻉。图一3.5.2 右值对象构造有拷⻉构造也有移动构造的场景图2展示了vs2019 debug环境下编译器对拷⻉的优化左边为不优化的情况下两次移动构造右边为编译器优化的场景下连续步骤中的拷⻉合⼆为⼀变为⼀次移动构造。需要注意的是在vs2019的release和vs2022的debug和release下⾯代码优化为⾮常恐怖会直接将str对象的构造str拷⻉构造临时对象临时对象拷⻉构造ret对象合三为⼀变为直接构造。要理解这个优化要结合局部对象⽣命周期和栈帧的⻆度理解如图3所示。linux下可以将下⾯代码拷⻉到test.cpp⽂件编译时⽤g test.cpp -fno-elide-constructors的⽅式关闭构造优化运⾏结果可以看到图1左边没有优化的两次移动。图二图三3.5.3 右值对象赋值只有拷⻉构造和拷⻉赋值没有移动构造和移动赋值的场景图4左边展示了vs2019 debug和g test.cpp -fno-elide-constructors关闭优化环境下编译器的处理⼀次拷⻉构造⼀次拷⻉赋值。需要注意的是在vs2019的release和vs2022的debug和release下⾯代码会进⼀步优化直接构造要返回的临时对象str本质是临时对象的引⽤底层⻆度⽤指针实现。运⾏结果的⻆度我们可以看到str的析构是在赋值以后说明str就是临时对象的别名。图四3.5.4 右值对象赋值既有拷⻉构造和拷⻉赋值也有移动构造和移动赋值的场景图5左边展示了vs2019 debug和g test.cpp -fno-elide-constructors关闭优化环境下编译器的处理⼀次移动构造⼀次移动赋值。需要注意的是在vs2019的release和vs2022的debug和release下⾯代码会进⼀步优化直接构造要返回的临时对象str本质是临时对象的引⽤底层⻆度⽤指针实现。运⾏结果的⻆度我们可以看到str的析构是在赋值以后说明str就是临时对象的别名。图五

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询