2026/6/1 12:41:33
网站建设
项目流程
唐山网站专业制作,老薛主机 wordpress 打不开,电子商务网站后台核心管理,本地网站模版批量修改网站字符一. 类的新功能#xff1a;更精细的默认函数与初始化控制
C11 扩展了类的核心能力#xff0c;允许开发者更灵活地控制默认成员函数、初始化方式#xff0c;解决了传统 C 中类设计的诸多痛点。
1.1 新增默认成员函数#xff1a;移动构造与移动赋值
C98 有 6 个默认成员函…一. 类的新功能更精细的默认函数与初始化控制C11 扩展了类的核心能力允许开发者更灵活地控制默认成员函数、初始化方式解决了传统 C 中类设计的诸多痛点。1.1 新增默认成员函数移动构造与移动赋值C98 有 6 个默认成员函数C11 新增移动构造函数和移动赋值运算符重载专门用于 “窃取” 右值对象的资源提升效率。核心规则若未手动实现移动构造 / 移动赋值且未实现析构、拷贝构造、拷贝赋值中的任意一个编译器会自动生成默认移动构造 / 移动赋值默认移动构造 / 赋值对内置类型成员逐字节拷贝(浅拷贝)对自定义类型成员调用其移动构造 / 赋值无则调用拷贝构造/拷贝赋值若手动提供移动构造 / 赋值编译器不再自动生成拷贝构造 / 赋值。实际示例namespace Scy { class string { public: // ………… // 移动构造窃取右值资源 string(string s) { cout string(string s) -- 移动构造 endl; swap(s); // 交换当前对象与右值对象的资源 } // 移动赋值窃取右值资源 string operator(string s) { cout string operator(string s) -- 移动赋值 endl; swap(s); return *this; } private: char* _str nullptr; size_t _size 0; size_t _capacity 0; }; } 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 operator(const Person p) { if(this ! p) { _name p._name; _age p._age; } return *this; }*/ /*~Person() {}*/ private: Scy::string _name; int _age; }; int main() { Person s1; Person s2 s1; Person s3 std::move(s1); Person s4; s4 std::move(s2); return 0; }1.2 成员变量声明时给缺省值 final与override成员变量给缺省值直接在类中声明成员变量时指定缺省值初始化列表未显式初始化时会使用该缺省值class Person { private: Scy::string _name 张三; // 声明时给缺省值 int _age 18; };final和override好的这是一个关于 Cfinal和override关键字的简单使用场景对比表格。关键字作用对象主要目的简单使用场景举例final类禁止该类被继承。class Base final { };class Derived : public Base { };//错误无法继承 final 类final虚函数禁止该虚函数在派生类中被重写。virtual void func() final { }void func() override { }//错误无法重写 final 函数override虚函数显式声明意图重写基类的虚函数让编译器检查签名是否正确。virtual void baseFunc(int);void baseFunc(int) override;//正确void baseFunc(float) override;//错误函数签名不匹配核心思想总结final用于“禁止”进一步扩展继承或重写。override用于“明确”并“验证”重写行为防止因笔误导致的错误提高代码安全性。1.3 default 与 delete控制默认函数生成default强制编译器生成默认函数如手动实现拷贝构造后仍想保留默认移动构造delete禁止编译器生成默认函数如禁止拷贝构造避免对象拷贝。class Person { public: Person(const char* name , int age 0) : _name(name), _age(age) {} // 强制生成默认移动构造 Person(Person p) default; // 禁止拷贝构造类似ostream的设计 Person(const Person p) delete; private: Scy::string _name; int _age; };class Person { public: Person(const char* name 张三yyyyyyyyyyyy,int age 18) :_name(_name) ,_age(age) {} // C11 // delete之后就不会自动生成默认拷贝构造函数了,库里面的ostream就用到了 // Person(const Person p) delete; // default之后让编译器强制生成移动构造函数 /*Person(const Person p) default; Person(Person p) default;*/ ~Person() {} private: // C98--将该函数设置为private,并且只声明不定义,这样也能实现类似于C11中delete的作用 /*Person(const Person p);*/ string _name; int _age; }; int main() { Person s1; Person s2 s1; Person s3 std::move(s1); //Person s4(************************, 1); //s4 std::move(s2); return 0; }1.4 其他类新功能补充委托构造 继承构造委托构造一个构造函数调用同类的另一个构造函数减少代码冗余。其中被委托的构造函数必须初始化所有成员变量,因为委托构造函数后(也是因为所有成员都会走初始化列表)不能再重复初始化了。继承构造通过using Base::Base继承基类所有的普通构造函数(特殊的拷贝构造函数/移动构造函数不继承)简化派生类设计继承构造函数中派生类自己的成员变量如果有缺省值会使用缺省值初始化如果没有缺省值那么跟之前类似内置类型成员不确定自定义类型成员使用默认构造初始化,但是派生类有自己的成员需要初始化时一般不适合用继承构造。// 委托构造 class Example { public: Example(int a, int b) :_x(a) , _y(b) { cout 目标构造函数\n; } Example(int a) :Example(a, 0) { cout 委托构造函数\n; } int _x; int _y; }; class Time { public: Time(int h,int m) :_hour(h) ,_minute(m) {} //error C3511: “Time”: 对委托构造函数的调用应仅为成员初始值设定项,被委托函数必须已经实现了全部成员变量的初始化 // error C2437 : “_second”: 已初始化 Time(int h, int m, int s) :Time(h, m) // , _second(s) {} private: int _hour; int _minute; int _second 0; }; int main() { Example(1, 2); Example(1); return 0; }// 继承构造 class Base { public: Base(int x,double d) :_x(x) ,_d(d) {} Base(int x) :_x(x) {} Base(double d) : _x(d) {} protected: int _x 0; double _d 0; }; // 传统的派生类实现构造,很麻烦复杂 //class Derived : public Base { //public: // Derived(int x) : Base(x) {} // Derived(double d) : Base(d) {} // Derived(int x, double d) : Base(x, d) {} //}; // C11继承基类的所有构造函数 class Derived : public Base { public: using Base::Base; // 这里需要注意的是这样的继承构造仅仅适合子类没有变量需要构造的,或者通过缺省值就可以了的。 /*protected: int _i 0; string _s;*/ }; int main() { Derived d1(1); Derived d2(1.1); Derived d3(2, 2.2); return 0; }二. STL中的一些变化下图中圈起来的就是STL中新的容器但是实际最有用的是 unordered_map 和 unordered_set。这两个我们前面已经进行了非常详细的讲解,其他的大家了解一下即可。STL中容器的新接口也不少最重要的就是右值引用和移动语义相关的push/insert/emplace系列接口和移动构造与移动赋值还有initializer_list版本的构造等,这些前面都有讲过还有一些无关痛痒的如cbegin/cend等需要时查查文档即可。容器的范围 for 遍历这个在容器部分也讲过了。三. lambda 表达式简洁的匿名函数对象lambda表达式本质是“匿名仿函数”可在函数内部定义无需单独声明类极大简化可调用对象的定义。3.1 lambda 核心语法lambda表达式语言使用层而言没有类型所以我们一般是用auto或者模版参数定义的对象去接受lambda对象格式[capture-list] (parameters) - return-type { function-body }[capture-list]捕捉列表不可省捕捉外层作用域变量供函数体使用捕捉列表总是出现在 lambda 函数的开始位置编译器根据 [] 来判断接下来的代码是否为 lambda 函数捕捉列表能够捕捉上下文的变量供函数使用捕捉列表可以传值或传引用捕捉具体的后面还会再讲。捕捉列表为空也不能省略。(parameters)参数列表可省与普通函数的参数列表功能类似无参数时可省略- return-type返回值类型可省用追踪返回类型形式声明函数的返回值类型没有返回值的时候这部分可以直接省略。一般返回值类型明确的情况下也可以省略编译器可自动推导但还是建议写一下的另外这种形式普通函数也可以用但是用的比较少。{function-body}函数体不可省函数体内的实现跟普通函数完全类似在该函数体内除了可以使用其参数外还可以使用所以捕获到的变量函数体就算为空也不可以省略。简单示例演示// 无捕捉、无参数、返回值自动推导 auto func1 [] { cout hello Lotso endl; }; func1(); // 输出hello Lotso // 有参数、有返回值 auto add [](int x, int y) - int { return x y; }; cout add(1, 2) endl; // 输出3// 关于返回值后置的 // std::mapstd::string, std::pairstd::string, std::string::iterator func(); // auto func()-std::mapstd::string, std::pairstd::string, std::string::iterator; int main() { // 一个简单的lamba表达式 // 这里可以只靠auto推导返回值类型,但是还是建议写出来的 /*auto add1 [](int x, int y){return x y; };*/ auto add1 [](int x, int y)-int {return x y; }; cout add1(1, 2) endl; // 关于那些东西可以省略哪些不可以 // 1. 就算捕捉为空也是不可以省略的 // 2. 参数为空可以直接省,()都不用了 // 3. 返回值可以省略,可以通过返回对象自动推导 // 4. 函数体不可以省略 auto func1 [] { cout hello Lotso endl; return 0; };//分号不要掉哈 func1(); int a 0, b 1; auto swap1 [](int x, int y) { int temp x; x y; y temp; }; swap1(a, b); cout a : b endl; return 0; }3.2 捕捉列表灵活复用外层变量捕捉列表控制外层变量的访问方式支持值捕捉、引用捕捉、混合捕捉核心规则如下捕捉方式含义示例[var]值捕捉var拷贝默认 const不可修改[a] { return a * 2; }[var]引用捕捉var可修改外层变量[a] { a; }[]隐式值捕捉所有使用的外层变量[] { return a b; }[]隐式引用捕捉所有使用的外层变量[] { a; b; }[, var]隐式值捕捉 显式引用捕捉var[, a] { a; return b; }[, var]隐式引用捕捉 显式值捕捉var[, a] { b; return a; }[this]捕捉当前类的 this 指针类成员函数中使用[this] { _a1; }关键注意值捕捉的变量默认被const修饰需修改时加mutable仅修改拷贝不影响外层变量全局变量、静态变量无需捕捉可直接使用捕捉列表不能为空无变量需捕捉时写[]。如果是在类里面就算是值捕捉成员变量也是可以修改的。实际示例int x 0; // 这里捕捉列表必须为空,因为全局变量不用捕捉就可以用,没有可以被捕捉的变量(静态成员变量也是同样的道理) auto func1 []() { x; }; int main() { // 只能用当前 lambda 局部域捕捉到的对象和全局对象 // 捕获列表的意义,本质是更方便的使用当前局部域的对象 int a 0, b 1, c 2, d 3; // 加了这个mutable之后值捕捉也可以修改a了,这里()就不可以省了就算没参数 // 传值捕捉本质是⼀种拷⻉,并且被const修饰了 // mutable相当于去掉const属性可以修改了 // 但是修改了不会影响外⾯被捕捉的值因为是⼀种拷⻉ /*auto func1 [a,b]() mutable*/ auto func1 [a, b] { // 值捕捉的变量不可以修改,引用捕捉的可以修改 //a; b; int ret a b; x;//全局变量 return ret; }; cout func1() endl; // 隐式值捕捉 // 用了哪些变量就捕捉哪些变量 auto func2 [] { int ret a b; return ret; }; cout func2() endl; // 隐式引用捕捉 // 用了哪些变量就捕捉哪些变量 auto func3 [] { a; c; d; }; func3(); cout a b c d endl; cout ********************* endl; // 混合捕捉的或一定是最前面的,而且如果前面是后面必须是引用捕捉,前面是后面必须是值捕捉 // 混合捕捉1 auto func4 [, a, b] { //a; //b; c; d; int ret a b c d; return ret; }; cout func4() endl; cout a b c d endl; // 混合捕捉2 auto func5 [, a, b] { a; b; /*c; d;*/ return a b c d; }; func5(); cout a b c d endl; // 局部的静态和全局变量不能捕捉也不需要捕捉 static int m 0; auto func6 [] { int ret x m; return ret; }; // 传值捕捉本质是⼀种拷⻉,并且被const修饰了 // mutable相当于去掉const属性可以修改了 // 但是修改了不会影响外⾯被捕捉的值因为是⼀种拷⻉ auto func7 []()mutable { a; b; c; d; return a b c d; }; cout func7() endl; cout a b c d endl; }在类里面的使用class A { public: void func() { int x 0, y 1; auto f1 [] { // 为什么是值捕捉,但是成员变量还可以呢,因为这里实际上捕捉的是他的this指针 _a1; return x y _a1 _a2; }; cout f1() endl; auto f2 [] { x; _a1; return x y _a1 _a2; }; cout f2() endl; // 捕捉this指针的本质就是为了可以访问成员变量 auto f3 [x,this] { _a1; return x _a1 _a2; }; cout f3() endl; } private: int _a1 0; int _a2 1; }; int main() { A a; a.func(); return 0; }3.3 lambda 的应用场景替代仿函数与函数指针传统排序需要定义仿函数lambda则简洁高效在学习 lambda 表达式之前我们使用的可调用对象只有函数指针和仿函数对象函数指针的类型定义起来比较麻烦仿函数则是要定义一个类相对会比较麻烦。使用 lambda 去定义可调用对象即简单又方便。lambda 在很多其他地方用起来也是很好用的。比如线程中定义线程的执行函数逻辑智能指针中定制删除器等lambda 的应用还是很广范的以后我们会不断接触到。实际使用示例struct Goods { string _name; // 名字 double _price; // 价格 int _evaluate; // 平价 // ... Goods(const char* str, double price, int evaluate) :_name(str) , _price(price) , _evaluate(evaluate) { } }; //struct ComparePriceLess //{ // bool operator()(const Goods gl, const Goods gr) // { // return gl._price gr._price; // } //}; // //struct ComparePriceGreater //{ // bool operator()(const Goods gl, const Goods gr) // { // return gl._price gr._price; // } //}; // //struct CompareEvaluateLess //{ // bool operator()(const Goods gl, const Goods gr) // { // return gl._evaluate gr._evaluate; // } //}; // //struct CompareEvaluateGreater //{ // bool operator()(const Goods gl, const Goods gr) // { // return gl._evaluate gr._evaluate; // } //}; int main() { vectorGoods v { { 苹果, 2.1, 5 }, { 香蕉, 3, 4 }, { 橙子, 2.2, 3}, { 菠萝, 1.5, 4 } }; // 类似这样的场景我们实现仿函数对象或者函数指针支持商品中 // 不同项的比较相对还是比较麻烦的那么这里lambda就很好用了 //sort(v.begin(), v.end(), ComparePriceLess()); //sort(v.begin(), v.end(), ComparePriceGreater()); //sort(v.begin(), v.end(), CompareEvaluateLess()); //sort(v.begin(), v.end(), CompareEvaluateGreater()); //auto priceLess [](const Goods gl, const Goods gr) //{ // return gl._price gr._price; //}; //sort(v.begin(), v.end(), priceLess); // 这样写更简单,但是调试查看的时候需要将断点打到下一个sort或下一个语句上 // 因为如果打到当前sort,他即算打到sort又算打到lambda sort(v.begin(), v.end(), [](const Goods gl, const Goods gr){ return gl._price gr._price; }); sort(v.begin(), v.end(), [](const Goods gl, const Goods gr) { return gl._price gr._price; }); sort(v.begin(), v.end(), [](const Goods gl, const Goods gr) { return gl._evaluate gr._evaluate; }); sort(v.begin(), v.end(), [](const Goods gl, const Goods gr) { return gl._evaluate gr._evaluate; }); return 0; }注意上面的程序最后那些sort需要调试的话可以按注释的方式来做3.4 lambda 的原理底层是仿函数编译器会将lambda表达式编译为一个匿名仿函数类捕捉列表的变量成为该类的成员变量operator()的参数、返回值、函数体与 lambda 一致不同 lambda 对应不同的仿函数类类名由编译器自动生成。补充说明lambda 的原理和范围 for 很像编译后从汇编指令层的角度看压根就没有 lambda 和范围 for 这样的东西。范围 for 底层是迭代器而 lambda 底层是仿函数对象也就说我们写了一个 lambda 以后编译器会生成一个对应的仿函数的类。仿函数的类名是编译器按一定规则生成的保证不同的 lambda 生成的类名不同lambda 参数 / 返回类型 / 函数体就是仿函数 operator () 的参数 / 返回类型 / 函数体lambda 的捕捉列表本质是生成的仿函数类的成员变量也就是说捕捉列表的变量都是 lambda 类构造函数的实参当然隐式捕捉编译器要看使用哪些就传那些对象。上面的原理我们可以透过汇编层了解一下下面第二段汇编层代码印证了上面的原理。实际示例int x 0; // lambda参数/返回类型 / 函数体就是仿函数operator()的参数 / 返回类型 / 函数体 // lambda 的捕捉列表本质是生成的仿函数类的成员变量也就是说捕捉列表的变量都是 lambda 类构造函数的实参 int main() { int a 0, b 1, c 2, d 3; //class lambada5(const int a_, int b_) //也行但是int没必要 class lambada5 { public: lambada5(int a_, int b_) :a(a_) ,b(b_) {} int operator()(int x) { //a; b; return a b x; } private: const int a;//对应值捕捉 int b;// 对应引用捕捉 }; // 可以看看汇编层 auto func [a, b](int x) { //a; b; return a b x; }; cout func(1) endl; // 等价于 /*lambada5 func(a, b);*/ return 0; }四. 包装器统一可调用对象的类型C 中的可调用对象包括函数指针、仿函数、lambda、成员函数但它们类型各异难以统一管理。std::function和std::bind包装器解决了这一问题。4.1 std::function可调用对象的 “容器”std::function是类模板可包装任意符合 “返回值 (参数类型)” 签名的可调用对象统一类型接口。参考文档function - C Reference补充说明std::function是一个类模板也是一个包装器。std::function的实例对象可以包装存储其他的可以调用对象包括函数指针、仿函数、lambda、bind 表达式等存储的可调用对象被称为std::function的目标。若std::function不含目标则称它为空。调用 空std::function的目标导致抛出std::bad_function_call异常。以上是function的原型他被定义functional头文件中。std::function- cppreference.com 是function的官方文件链接。函数指针、仿函数、lambda 等可调用对象的类型各不相同std::function的优势就是统一类型对他们都可以进行包装这样在很多地方就方便声明可调用对象的类型下面的第二个代码样例展示了std::function作为 map 的参数实现字符串和可调用对象的映射表功能。核心用法#includefunctional int f(int a, int b) { return a b; } struct Functor { public: int operator()(int a, int b) { return a b; } }; class Plus { public: Plus(int n 10) :_n(n) {} static int plusi(int a, int b) { return a b; } double plusd(double a, double b) { return (a b) * _n; } private: int _n; }; // // int main() { // 类型擦除 functionint(int, int)f1 f; functionint(int, int)f2 Functor(); functionint(int, int)f3 [](int a, int b) {return a b; }; cout f1(1, 1) endl; cout f2(1, 1) endl; cout f3(1, 1) endl; vectorfunctionint(int, int) v; v.push_back(f); v.push_back(Functor()); v.push_back([](int a, int b) {return a b; }); for (auto f : v) { cout f(1, 1) endl; } // 静态成员函数,下面两种写法都可以,用第二种可以统一规范 // functionint(int, int) f4 Plus::plusi; functionint(int, int) f4 Plus::plusi; cout f4(1, 1) endl; // 成员函数,必须带,并且不要忘了this指针的存在,所以下面实际是三个参数,但是写法很多 functiondouble(Plus*, double, double) f5 Plus::plusd; Plus ps; cout f5(ps, 1.1, 1.1) endl; functiondouble(Plus, double, double) f6 Plus::plusd; // Plus ps; cout f6(ps, 1.1, 1.1) endl; functiondouble(Plus, double, double) f7 Plus::plusd; cout f7(Plus(), 1.1, 1.1) endl; functiondouble(Plus, double, double) f8 Plus::plusd; cout f8(Plus(), 1.1, 1.1) endl; auto pf1 Plus::plusd; Plus* ptr ps; cout (ps.*pf1)(1.1, 1.1) endl; cout (ptr-*pf1)(1.1, 1.1) endl; return 0; }关于类型擦除大家可以自己下去再了解一下这里就不过多介绍了。4.2 std::bind可调用对象的 “适配器”std::bind是函数模板可调整可调用对象的参数个数、顺序绑定固定参数返回一个新的可调用对象。参考文档bind - C Reference补充说明核心用法(有的地方需要用到上面的代码这里就不重复写了)#includefunctional using placeholders::_1; using placeholders::_2; using placeholders::_3; int Sub(int a, int b) { return (a - b) * 10; } int SubX(int a, int b, int c) { return (a - b - c) * 10; } int main() { // bind 本质返回一个仿函数对象 // 调整参数顺序(不常用) // _1 代表第一个实参 // _2 代表第二个实参 // ………… auto f1 bind(Sub, _1, _2); auto f2 bind(Sub, _2, _1); // _1 代表第一个实参 // _2 代表第二个实参 cout f1(10, 5) endl; cout f2(10, 5) endl; // 调整参数个数 auto f3 bind(SubX, 10, _1, _2); cout f3(15, 5) endl; // _1 代表第一个实参 // _2 代表第二个实参 // 底层operator(),调整SubX,第一个参数10,15,5 auto f4 bind(SubX, _1, 10, _2); cout f4(15, 5) endl; // 底层operator(),调整SubX,第一个参数15,10,5 auto f5 bind(SubX, _1, _2, 10); cout f5(15, 5) endl; // 底层operator(),调用SubX,第一个参数15,5,10 // 利用bind改进 functiondouble(Plus, double, double)f7 Plus::plusd; cout f7(Plus(), 1.1, 1.1) endl; cout f7(Plus(), 2.2, 1.1) endl; cout f7(Plus(), 3.3, 1.1) endl; // 绑定成员函数需传入this指针或对象 functiondouble(double, double)f8 bind(Plus::plusd, Plus(), _1, _2); cout f8(1.1, 1.1) endl; cout f8(2.2, 1.1) endl; cout f8(3.3, 1.1) endl; return 0; }声明C11中还有智能指针,在后面会单独写一篇博客的核心总结与避坑指南1. 类新功能避坑手动实现拷贝构造 / 赋值后默认移动构造 / 赋值不再生成需手动添加或用defaultdelete可禁止拷贝如单例模式default可强制生成默认函数如移动构造。2. lambda 避坑值捕捉的变量默认const修改需加mutable仅影响拷贝引用捕捉需确保外层变量生命周期长于lambda避免悬垂引用。3. 包装器避坑function包装成员函数时需传入this指针或对象隐含第一个参数bind的占位符_n对应新可调用对象的第 n 个参数顺序不可混淆。