尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C++(19)——C++11

C++(19)——C++11 C111. 统一的列表初始化d1.1 {}初始化在C98中标准允许使用花括号{}对数组或者结构体元素对统一的列表初始化值设定C11扩大了其使用范围使其可用于所有内置类型和自定义类型。class Date { public: Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) { cout Date(int year, int month, int day) endl; } private: int _year; int _month; int _day; }; struct Point { int _x; int _y; }; int main() { int array1[]{ 1, 2, 3, 4, 5 }; int array2[5]{ 0 }; //Point p{ 1, 2 }; 可以添加等号也可以不加 Point p { 1, 2 }; Date d1(2022, 1, 1); // old style // 创建对象时也可以使用列表初始化调用构造函数初始化 Date d2{ 2022, 1, 2 }; Date d3 { 2022, 1, 3 }; // C11中列表初始化也可以适用于new表达式中 int* pa new int[4]{ 0 } return 0; }1.2 initializer_list文档介绍本质内部只保存了两个指针首元素、末尾后一位不存储数据副本底层数据存在一块临时数组里templateclass T class initializer_list { const T* _begin; const T* _end; public: // 成员函数 const T* begin() const; const T* end() const; size_t size() const; };特点元素只读指针是const*不支持增删操作拷贝initializer_list只是拷贝两个指针作用接收大括号{ }初始化列表的数据一般作为构造函数的参数也可以作为operator的参数vectorint v {1,2,3,4,5}; //{1,2,3,4,5}被编译器包装成一个initializer_listint对象传给vector对应构造函数2. 声明2.1 autoC11中auto用于实现自动类型推断要求必须进行显示初始化让编译器将自定义对象的类型设置为初始化的类型2.2 decltype作用将变量的类型声明为表达式指定的类型int main() { const int x 1; double y 2.2; decltype(x * y) ret; // ret的类型是double decltype(x) p; // p的类型是int* cout typeid(ret).name() endl; cout typeid(p).name() endl; return 0; }2.3 nullptr由于C中NULL被定义为字面量0既能表示整形常量也能表示指针常量所有C11中新增了nullptr用于表示空指针3. STL中的变化新容器重点unordered_set、unordered_maparray、forward_list用处不大新方法部分新方法用得比较少比如提供了cbegin和cend方法返回const迭代器而begin和end本身就可以返回const迭代器4. 右值引用和移动语义4.1 左值引用和右值引用传统C已有引用语法称为左值引用C中新增了右值引用语法都是给对象取别名左值可以获取其地址可以对其赋值左值引用专门用来绑定左值。特殊const T 既能绑定左值也能绑定右值右值不能获取其地址临时对象可以出现在赋值号的右边不能在赋值号的左边分为纯右值和将亡值如函数返回的临时对象右值引用专门用来绑定右值。重点右值引用本身是一个左值右值引用可以引用move后的左值本质目的识别出临时对象实现资源转移规避深拷贝开销4.2 右值引用使用场景场景1移动构造函数、移动赋值运算符右值引用诞生的最根本用途移动构造函数本质是将窃取参数右值的资源占为己有不用进行深拷贝// 移动构造 string(string s) { _str s._str; s._str nullptr; } // 移动赋值 string operator(string s) { swap(s); return *this; }场景2使用move函数强制转移左值对象资源移动完成后对象处于有效但未定义状态不能再被读写vectorint v1 {1,2,3,4}; vectorint v2 std::move(v1); // v1资源被移动到v2之后v1变为空场景3万能引用std::forward -完美转换模板场景在调用函数传参时无论传的是左值还是右值进入函数后其本身都会变成左值使下层函数只能收到左值无法触发移动语义。此时需要forward函数使右值依旧为右值templateclass T void Func(T t) // 万能引用 { OtherFunc(std::forwardT(t)); // 原样转发保留左右值属性 }场景4函数重载区分左值、右值版本void Print(string s) // 左值版本对象长期存在 { cout左值版本sendl; } void Print(string s) // 右值版本临时对象可以偷资源 { cout右值临时对象版本endl; }5. lambda表达式lambda是一个匿名的可调用对象匿名函数本质自动生成一个匿名仿函数类语法格式[捕获列表](参数列表) mutable - 返回值类型 { 函数体; };[ ] 捕获列表把外部作用域的变量拷贝副本捕获到 lambda 内部使用默认是const( ) 参数列表与普通函数的参数列表一样mutable 可写可不写写了之后取消const属性允许修改- 返回值类型 可省略编译器会自动推导捕获列表说明[ ] 不捕获任何变量[] 值捕获父作用域中的所有变量只读不可修改[] 引用捕获父作用域中的所有变量可修改[x] 仅捕获x变量[x] 仅引用捕获x变量[, x] 默认全部值捕获仅x引用捕获[, x] 默认全部引用捕获仅x值捕获[this] 捕获当前类对象的this指针可访问成员变量注意点1. 父作用域指包含lambda函数的语句块2. 捕获列表可由多个捕获项组成以逗号分隔。比如[, a, b]3. 不允许重复捕获。比如[, a] 已经值捕获了包含a的所有变量再值捕获a出现了重复4. lambda表达式之间不能相互赋值其类型看起来相同在底层实则不同6. 可变参数模板普通模板只能接收固定数量参数可变参数模板 可以接收任意个数、任意类型的参数templateclass ...Args void Show(Args... args) {} Show(1); //一次函数调用可以传入0个、1个、多个不同类型参数 Show(1, 3.14, hello, string(bit));... 包展开符合Args模板参数包代表若干类型。不能单独拿出来使用不是一个类型args函数参数包代表若干实际参数值遍历参数包参数包不能直接遍历不支持下标访问或for循环遍历方法1递归拆解// 终止函数没有参数递归停下 void Show() { cout 递归结束 endl; } // 可变参数递归函数 templateclass T, class ...Args void Show(const T val, Args... args) { cout val ; // 先处理第一个参数 Show(args...); // 剩下的参数继续递归参数包不断缩小 } int main() { Show(10, 3.14, abc); return 0; }方法2逗号表达式初始化列表展开templateclass ...Args void Print(Args... args) { int arr[] { (cout args , 0)... };//逗号表达式展开 } int main() { Print(1, 2.5, hello); //展开之后编译器生成的代码等价于 //int arr[] { (cout1 ,0), (cout2.5 ,0), (couthello ,0) }; return 0; }C的特性初始化列表通过初始化一个数组来执行参数包的展开构造出的数组没有任何意义只是为了在初始化过程中展开参数包empalce系列接口vector、list等顺序容器中提供了emplace系列接口1. empalce_back(Args... args)尾部原地构造对象对应 push_back2. empalce_front(Args... args)头部原地构造对象对应 push_front3. empalce_(pos, Args... args)在迭代器位置原地构造对应 insertpush 系列先构造出对象再拷贝/移动进容器emplace系列直接在容器的内存空间里原地构造对象省去拷贝、移动vectorstring v; v.push_back(string(hello)); v.emplace_back(hello);对比push_back 在函数栈帧上生成 string(hello) 临时对象右值调用移动构造把临时对象资源移动到容器内部最后销毁临时对象empalce_back传入构造对象所需要的参数容器直接在自己内部内存上调用string(hello)的构造函数构造出对象。对比push_back 省去一次移动操作7. 包装器function包装器std::function 是一个类型容器也叫适配器其本质是一个类模板可以把各种可调用对象包装成同一个类型4种可调用对象普通函数、函数指针、仿函数 、lambda表达式语法格式std::function 返回值类型(参数类型列表) 变量名;代码演示using namespace std; //普通函数 int Add(int a, int b) { return a b; } //仿函数 struct Sub { int operator()(int a, int b) { return a - b; } }; int main() { //1.包装普通函数 functionint(int, int) f1 Add; cout f1(3, 5) endl; //2.包装仿函数 functionint(int, int) f2 Sub(); cout f2(10, 2) endl; //3.包装lambda functionint(int, int) f3 [](int x, int y) { return x * y; }; cout f3(4, 5) endl; return 0; }8. 新的类功能在之前的C中有6个默认成员函数1. 构造函数 2. 析构函数3. 拷贝构造函数 4. 拷贝赋值重载5. 取地址重载 6. const取地址重载C11中新增了两个移动构造函数 和 移动赋值重载函数注意点1. 如果没有自己实现移动构造函数 / 移动赋值重载函数且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个那么编译器会自动生成一个默认移动构造/默认移动赋值对于内置类型成员会执行逐成员按字节拷贝浅拷贝对于自定义类型成员需要看这个成员是否实现移动构造/移动赋值如果实现了就调用移动构造 / 移动赋值如果没有就调用拷贝构造2. 如果提供了移动构造或者移动赋值编译器不会自动提供拷贝构造和拷贝赋值强制生成默认函数的关键字defualt如上述情况如果我们提供了拷贝构造就不会生成移动构造了这时可以使用defualt关键字显示指定移动构造生成class Person { public: Person(const char* name , int age 0) :_name(name) , _age(age) {} Person(const Person p) :_name(p._name) ,_age(p._age) {} Person(Person p) default;//指定生成移动构造 private: bit::string _name; int _age; };禁止生成默认函数的关键字delete用法同上
返回列表