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

【C++11】lambda匿名函数、包装器、新的类功能

目录

前言

一、lambda匿名函数

二、包装器

三、新的类功能

总结



前言

        本文接着介绍C++11中的:lambda匿名函数、包装器function和bind、以及类的一些新功能。


一、lambda匿名函数

1.基础语法

  • lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。
  • lambda 表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接收 lambda 对象。
  • lambda 表达式的格式:[capture-list] (parameters)-> return type { function boby }
  • [capture-list]:捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据 [ ] 来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,具体细节下面捕获列表中我们再细讲。捕捉列表为空也不能省略[ ]
  • (parameters):参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()⼀起省略。
  • ->return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。注意:返回值类型可省略,并且是连->一起省略
  • { function boby }:函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略

一个简单的lambda演示:

#include <iostream>
using namespace std;int main()
{//使用auto进行接收auto add1 = [](int x, int y)->int {return x + y; };cout << add1(2, 4) << endl;return 0;
}

运行结果:

  • 上面的 add1 其实我们可以当成仿函数对象理解,lambda的底层其实就是仿函数。
  • 然后lambda可以写得简洁一点:满足以下要求即可
  • 1、捕捉为空也不能省略[ ]
  • 2、参数为空可以省略()
  • 3、返回值可以省略->,可以通过返回对象自动推导
  • 4、函数体不能省略{}

一般可以不用写返回值,一般写返回值是为了可读性

#include <iostream>
using namespace std;int main()
{// 1、捕捉为空也不能省略// 2、参数为空可以省略// 3、返回值可以省略,可以通过返回对象自动推导// 4、函数体不能省略auto func1 = []{cout << "hello world" << endl;return 0;};func1();return 0;
}

运行结果:


2.应用场景

  • 在学习 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义一个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既简单又方便。
  • 所以 lambda 在一些仿函数多的场景,把仿函数替换成 lambda 后会简洁很多。

比如下面这个场景,需要对一类商品从名字、价格、评价三个方面分别进行排序:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;//商品
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;}
};int main()
{vector<Goods> v = { {"苹果",2.1,5},{"香蕉",3,4},{"橙子",2.2,3},{"菠萝",1.5,4} };//排序sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());return 0;
}
  • 以上是使用仿函数的方法,我们仅是对商品的价格进行排序,我们就要写两个仿函数的类,那如果我们还要对商品的名称、评价进行排序,就还要写4个仿函数类。
  • 是不是有点繁琐,这时候如果我们把仿函数换成 lambda 匿名函数,并且直接定义在 sort 的第三个参数位置上,是不是就不用这么麻烦了

使用 lambda 简化:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;//商品
struct Goods
{string _name;	//名字double _price;	//价格int _evaluate;	//评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};int main()
{vector<Goods> v = { {"苹果",2.1,5},{"香蕉",3,4},{"橙子",2.2,3},{"菠萝",1.5,4} };//1.按价格升序sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr){return gl._price > gr._price;});//2.按价格降序sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr){return gl._price < gr._price;});//3.按名称升序sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr){return gl._name > gr._name;});//4.按名称降序sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr){return gl._name < gr._name;});//5.按评价升序sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr){return gl._evaluate > gr._evaluate;});//6.按价格降序sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr){return gl._evaluate < gr._evaluate;});return 0;
}
  • 除此之外,lambda 在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等,lambda 的应用还是很广泛的,以后我们会不断接触到。


3.捕捉列表

  • lambda 表达式中只能默认用 lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉。
  • 第一种捕捉方式:是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。[x, y,&z]表示x和y值捕捉,z引用捕捉。

需要注意:传值捕捉的变量不能修改,传引用捕捉的变量能修改

#include <iostream>
using namespace std;int main()
{int a = 0, b = 1;auto func1 = [a, &b]{//a++;  //值捕捉对象不能修改b++;	//引用捕捉可以修改return a + b;};cout << func1() << endl;return 0;
}

运行结果:

  • 第二种捕捉方式:是在捕捉列表中隐式捕捉,我们在捕捉列表写⼀个 = 表示隐式值捕捉,在捕捉列表写一个 & 表示隐式引用捕捉,这样我们 lambda 表达式中用了那些变量,编译器就会自动捕捉那些变量。
  • 注意在底层中,并不是所有变量真的都被捕捉到 lambda 中,而是用了哪些就捕捉哪些。
#include <iostream>
using namespace std;int main()
{int a = 0, b = 1, c = 2, d = 3;//1.隐式值捕捉auto func1 = [=]{return a + b + c + d;};cout << func1() << endl;cout << "a:" << a << " b:" << b << " c:" << c << " d:" << d << endl << endl;//2.隐式引用捕捉auto func2 = [&]{a++;b++;c++;d++;return a + b + c + d;};cout << func2() << endl;cout << "a:" << a << " b:" << b << " c:" << c << " d:" << d << endl;return 0;
}

运行结果:

  • 第三种捕捉方式:是在捕捉列表中混合使用隐式捕捉和显式捕捉。[=,&x]表示其他变量隐式值捕捉, x引用捕捉;[&,x,y]表示其他变量引用捕捉,x和y值捕捉。当使用混合捕捉时,第一个元素必须是 &或=,并且&混合捕捉时,后面的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必须是引用捕捉。

混合捕捉主要是需要逻辑自洽,不能第一个元素选择&隐式引用捕捉后,第二个又&x隐式引用捕捉指定元素x。

#include <iostream>
using namespace std;int main()
{int a = 0, b = 1, c = 2, d = 3;//混合捕捉auto func1 = [=, &a, &b]{//a,b引用捕捉,其余值捕捉a++;b++;return a + b + c + d;};cout << func1() << endl;cout << "a:" << a << " b:" << b << " c:" << c << " d:" << d << endl << endl;//2.混合捕捉auto func2 = [&, a, b]{//a,b值捕捉,其余引用捕捉c++;d++;return a + b + c + d;};cout << func2() << endl;cout << "a:" << a << " b:" << b << " c:" << c << " d:" << d << endl;return 0;
}

引用捕捉:


捕捉列表的捕捉范围:

  • lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉,lambda 表达式中可以直接使用,这就意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
  • 简单点说:函数局部域中以及 lambda 位置之前定义的变量都可以捕捉,捕捉列表不用捕捉静态变量和全局变量因为可以直接使用,所以 lambda 定义在全局中捕捉列表 [ ] 只能为空。
#include <iostream>
using namespace std;int x = 1;//全局的lambda,[]只能为空
auto func1 = []{x++;};int main()
{static int y = 2;//全局变量和静态变量不用捕捉,可以直接使用auto func2 = []{++x;++y;return x + y;};cout << func2() << endl;return 0;
}

运行结果:


mutable:

  • 默认情况下,lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。
  • 使用该修饰符后,参数列表不可省略(即使参数为空)。
#include <iostream>
using namespace std;int main()
{int a = 0, b = 1, c = 2, d = 3;//mutableauto func1 = [=]()mutable{++a;++b;++c;++d;return a + b + c + d;};cout << func1() << endl;cout << "a:" << a << " b:" << b << " c:" << c << " d:" << d << endl;return 0;
}

运行结果:

注意:实参a,b,c,d都没有被实际修改,只是函数内部修改了形参。


4.lambda的原理

  • lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,而lambda底层是仿函数对象,也就说我们写了一个 lambda 以后,编译器会生成一个对应的仿函数的类。
  • 仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体,lambda 的捕捉列表本质是生成的是仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传那些对象。

我们可以观察汇编指令层验证这些结论:

#include <iostream>
using namespace std;//定义仿函数计算年利润
class Rate
{
public:Rate(double rate): _rate(rate){}double operator()(double money, int year){return money * _rate * year;}
private:double _rate;
};int main()
{double rate = 0.49;//同样定义一个lambda计算年利润auto r2 = [rate](double money, int year){return money * rate * year;};//创建仿函数对象并调用Rate r1(rate);r1(10000, 2);//调用lambda对象r2(10000, 2);return 0;
}

反汇编:

  • 观察仿函数和lambda的汇编层,很明显,它们都是调用operator(),也就是仿函数,它们的汇编层指令也都非常类似。所以 lambda 没有创建新语法,其本质就是仿函数,只不过仿函数的创建都交给了编译器去实现。
  • 观察汇编层也可以看到lambda的仿函数的对象名是编译器自己决定的。


二、包装器

1.function

  • std::function 是一个类模板,也是一个包装器。std::function 的实例对象可以包装存储其它的可以调用对象,包括函数指针、仿函数、lambda、bind表达式等。存储的可调用对象称为 std::function 的目标。若 std::function 不含目标,则称它为空,调用空的 std::function 的目标导致抛出std::bad_function_call异常。
  • function 被定义在<functional>头文件中。
  • 函数指针、仿函数、 lambda 等可调用对象的类型各不相同,std::function 的优势就是统 一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型。

一般语法演示:

#include <iostream>
#include <functional>
using namespace std;//普通函数
int f(int a, int b)
{return a + b;
}//仿函数
struct Functor
{
public:int operator() (int a, int b){return a + b;}
};int main()
{//包装各种可调用对象function<int(int, int)> f1 = f;	//函数指针function<int(int, int)> f2 = Functor();	//仿函数function<int(int, int)> f3 = [](int x, int y) {return x + y; };	//lambda//使用方法和仿函数一样cout << f1(1, 1) << endl;cout << f2(2, 2) << endl;cout << f3(3, 3) << endl;return 0;
}

运行结果:

  • 以上是一般情况的包装方法,比如 function<int(int, int)> f,其第一个int是返回类型,()中的就是参数类型,这些都是随被包装函数的返回值、参数变化的。
  • function包装后,使用对象名()传参调用,因为function也实现了operator()接口,所以使用方法和仿函数类似。

包装器包装类的成员函数时,语法会有一些不一样的地方:

  • 首先不管什么成员函数,包装的成员函数都需要指定类域,类域前还需要加上&
  • 对于普通成员函数,首先包装器()里的第一个形参必须和类型名有关,可以是类型名的指针,可以是单纯的类型名,可以是类型名的左值引用,可以是类型名的右值引用。原因就是普通成员函数都有一个隐藏的指针参数——this指针。
  1. 如果包装器第一个参数位置是类型名的指针,那么传参时就只能传类对象的地址。
  2. 如果第一个参数位置就是单纯的类型名,那么可以直接传对象,包括匿名对象。
  3. 如果第一个参数位置是类型名的左值引用,那么就只能传左值,不包括匿名对象。
  4. 如果第一个参数位置是类型名的右值引用,那么就只能传右值,包括匿名对象。
  • 还有静态成员函数,静态成员函数因为没有this指针,所以不用传类名,除了函数指定类域加&符,其余和普通函数一样包装。(静态成员函数其实加不加&符都可以,但是统一一下方便记忆)
#include <iostream>
#include <functional>
using namespace std;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()
{//1.包装静态成员函数:指定类域function<int(int, int)> f1 = &Plus::plusi;	//&可加可不加cout << f1(2, 3) << endl << endl;//2.包装普通成员函数:指定类域+&//第一个参数用指针function<double(Plus*, double, double)> f2 = &Plus::plusd;Plus p1;cout << f2(&p1, 2.2, 3.3) << endl << endl;//第一个参数用类型名function<double(Plus, double, double)> f3 = &Plus::plusd;cout << f3(Plus(), 2.2, 3.3) << endl;cout << f3(p1, 2.2, 3.3) << endl << endl;//第一个参数用类类型的左值引用function<double(Plus&, double, double)> f4 = &Plus::plusd;cout << f4(p1, 2.2, 3.3) << endl << endl;//第一个参数用类类型的右值引用function<double(Plus&&, double, double)> f5 = &Plus::plusd;cout << f5(Plus(), 2.2, 3.3) << endl;cout << f5(move(p1), 2.2, 3.3) << endl;return 0;
}

运行结果:

  • 很明显,第一个参数用指针或者左值引用都是需要先定义一个类的对象,所以稍微麻烦一点。相对的,第一个参数用类类型名或者右值引用,就可以传匿名对象,比较简洁一点,当然如果类涉及深拷贝那么就使用右值引用最好。


2.function的使用场景

比如下面这道老题:

链接:150. 逆波兰表达式求值 - 力扣(LeetCode)

  • 所谓的逆波兰表达式就是一个字符串数组,里面有数字字符串,有运算符字符串:
  • 解决方法:遇到数字先存到栈里面,遇到运算符就把栈顶的两个数字出栈进行运算,运算结果接着入栈,直到遍历完字符串数组,把栈里面的最终结果返回就行。这题以前篇章讲过。

之前的解决方法:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for(auto& str : tokens){//操作符if("+"==str || "-"==str || "*"==str || "/"==str){int right = st.top();st.pop();int left = st.top();st.pop();switch(str[0]){case '+':st.push(left+right);break;case '-':st.push(left-right);break;case '*':st.push(left*right);break;case '/':st.push(left/right);break;}}else//操作数{st.push(stoi(str));}}return st.top();}
};

学了C++11的lambda和function后:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;map<string, function<int(int,int)>> opMap;opMap.emplace("+", [](int x,int y)->int{return x+y;});opMap.emplace("-", [](int x,int y)->int{return x-y;});opMap.emplace("*", [](int x,int y)->int{return x*y;});opMap.emplace("/", [](int x,int y)->int{return x/y;});for(auto& str : tokens){if(opMap[str]){int right = st.top();st.pop();int left = st.top();st.pop();st.push(opMap[str](left,right));}else{st.push(stoi(str));}}return st.top();}
};
    • 首先,我们老方法是使用 swicth 进行匹配字符,然后进行指定运算。
    • 新方法是利用map的K,V结果,将运算符与对应的运算函数进行映射,这里的V也就是运算函数,必须是可调用对象。刚好所有的运算参数类型返回值都一样,所以我们可以使用 function 进行包装,将运算函数写成 lambda 匿名函数进行包装。这样map的第二个参数类型就可以使用 function<int(int,int)> 了。
    • 这样一来不仅代码简洁了,后续也方便扩展,比如增加运算符运算函数,可以直接在map处增加即可。
    • function在实际中,也是用于映射的场景居多。


    3.bind

    • bind 是一个函数模板,它也是一个可调用对象的包装器,可以把他看做一个函数适配器,对接收的 fn 可调用对象进行处理后返回一个可调用对象。bind 可以用来调整参数个数和参数顺序。bind 也在这个<functional>头文件中。
    • 调用bind的一般形式:auto newCallable = bind(callable,arg_list); 其中 newCallable本身是⼀个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
    • arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是占位符,表示 newCallable 的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。_1/_2/_3....这些占位符放到placeholders的一个命名空间中,带着命名空间使用有点麻烦,平时可以需要用到几个占位符就用using展开几个即可。

    bind本质是返回一个仿函数对象,和匿名函数一样接收的话需要用auto接收

    1.bind调整参数顺序(不常用)

    #include <iostream>
    #include <functional>
    using namespace std;
    using placeholders::_1;	//代表第一个实参
    using placeholders::_2;	//代表第二个实参int Sub(int a, int b)
    {return (a - b) * 10;
    }int main()
    {//_1代表第一个实参,_2代表第二个实参//所以sub1没有改变什么auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl;//我们改变一下_1和_2的位置,就能改变实参传递的顺序auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;return 0;
    }

    运行结果:

    • bind用于改变参数顺序不常用,用法就是使用placeholders中的占位符进行调整。

    2.bind调整参数个数(常用)

    注意:调整参数个数的本质是将函数中某个或某些参数固定为某些值;另外,_1、_2...的本质是代表是代表第一个传参的位置、第二个传参的位置...,所以我们固定某一个值后,依旧是从_1开始控制第一个传参的位置,不是说第函数的第一个参数的位置固定值后_1就没有了。

    #include <iostream>
    #include <functional>
    using namespace std;
    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()
    {//将sub的第一个参数固定为100auto sub1 = bind(Sub, 100, _1);cout << sub1(20) << endl;//将sub的第二个参数固定为200auto sub2 = bind(Sub, _1, 200);cout << sub2(280) << endl;cout << "************" << endl;//分别绑死SubX的1,2,3参数auto subx1 = bind(SubX, 100, _1, _2);auto subx2 = bind(SubX, _1, 100, _2);auto subx3 = bind(SubX, _1, _2, 100);cout << subx1(20, 30) << endl;cout << subx2(100, 30) << endl;cout << subx2(100, 20) << endl;return 0;
    }
    • 总之,我们传给bind对象的第一个参数是给_1,第二个参数是给_2......
    • 而控制参数个数,则就是固定哪个形参的值,就把那个值写在对应位置。


    4.bind的使用场景

    • 第一个场景,比如和function结合,调整function参数个数。
    • 前面function包装类成员函数不是要传类对象之类的吗,我们把第一个参数固定死,就可以像普通函数一样使用。
    #include <iostream>
    #include <functional>
    using namespace std;
    using placeholders::_1;	//代表第一个实参
    using placeholders::_2;	//代表第二个实参class Plus
    {
    public:Plus(int n = 10):_n(n){}//普通成员函数double plusd(double a, double b){return (a + b) * _n;}
    private:int _n;
    };int main()
    {//bind固定第一个形参后,function也不用再加上类型名function<double(double, double)> f1 = bind(&Plus::plusd, Plus(), _1, _2);cout << f1(2.2, 3.3) << endl;return 0;
    }

    运行结果:

    function结合bind的结果,我们也能看出,定义function的参数列表(),()中的参数类型定义是根据实际传参个数和类型来的,上面我们固定了第一个位置传参匿名对象Plus(),然后function定义中就没有了类类型相关的定义。


    • 第二个场景,计算复利
    • 计算复利与计算年利润是不一样的,年利润是固定本金,每年只计算本金产生的利息。而复利虽然也是计算本金产生的利息,不同的是前一年产生的利息会加到本金上。
    • 我们实现一个计算复利的lambda函数,然后只传本金100000,固定年化利率和年限。看看不同的年化利率和年限产生的复利有多少。
    #include <iostream>
    #include <functional>
    using namespace std;
    using placeholders::_1;	//代表第一个实参int main()
    {//计算复利auto func1 = [](double rate, double money, int year)->double {double ret = money;for (int i = 0; i < year; i++){ret += ret * rate;}return ret - money;};//分别绑死不同的年化利率和年限,看看100000本金能产生多少复利function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);cout << func3_1_5(1000000) << endl;cout << func5_1_5(1000000) << endl;cout << func10_2_5(1000000) << endl;cout << func20_3_5(1000000) << endl;return 0;
    }

    运行结果:


    三、新的类功能

    1.默认的移动构造和移动赋值

    • 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重载/const 取地址重载,最后重要的是前4个,后两个用处不大,默认成员函数就是我们不写编译器会生成一个默认的。C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
    • 如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
    • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成⼀个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
    • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

    关于自动生成的移动构造和移动拷贝这里就不再演示了,主要是对上一篇移动语义的补充


    2.成员变量声明时给缺省值

    • 成员变量声明时给缺省值是给初始化列表用的,如果没有显示在初始化列表初始化,就会在初始化列表中用这个缺省值去初始化。
    • 这个我们在类和对象(下)就讲过,这里注意是主要它是C++11新增的语法

    3.defult和delete

    defult:

    • C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用 default 关键字显示指定移动构造生成。
     Person(Person&& p) = default;

    delete:

    • 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明不定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
    • 比如不行让某类被拷贝构造,就可以用=delete把拷贝构造给禁掉。
     Person(const Person& p) = delete;


    4.final与override

    • 这两个关键字在多态的篇章中详细讲过
    • final 就是放在虚函数后面修饰,让派生类不能再重写该虚函数。
    • override 也是放在虚函数后面,它的作用是检查该虚函数是否完成重写,比如基类没有该虚函数就会报错。

    5.STL中的一些变化

    • 下图圈起来的就是STL中的新容器,但是实际最有用的是unordered_map和unordered_set。这两个我们前面的文章已经进行了详细的讲解,其他的大家了解一下即可。
    • STL中容器的新接口也不少,最重要的就是右值引用和移动语义相关的push/insert/emplace系列接口和移动构造和移动赋值,还有initializer_list版本的构造等,这些前面都讲过了,还有一些无关痛痒的如cbegin/cend等需要时查查文档即可。
    • 容器的范围for遍历,这个在容器部分也讲过了。


    总结

            好啦,以上就是本文的全部内容了,感谢支持!


    文章转载自:

    http://faUl0pDF.mLwjr.cn
    http://i6cIIvit.mLwjr.cn
    http://UDBaQEDX.mLwjr.cn
    http://4SKTI8Rt.mLwjr.cn
    http://SCbhWQId.mLwjr.cn
    http://OmPcjb3W.mLwjr.cn
    http://yERLswJn.mLwjr.cn
    http://EGD5p1Z1.mLwjr.cn
    http://OUKnrTac.mLwjr.cn
    http://Rokrr4R7.mLwjr.cn
    http://BSzWHXNu.mLwjr.cn
    http://aSwE28aT.mLwjr.cn
    http://qm0sASV4.mLwjr.cn
    http://itMsS1xD.mLwjr.cn
    http://JDW6qHTH.mLwjr.cn
    http://yzshksFz.mLwjr.cn
    http://VI6Z0ccb.mLwjr.cn
    http://ocGjM6Rb.mLwjr.cn
    http://XHrxXb9V.mLwjr.cn
    http://pU0iT5TZ.mLwjr.cn
    http://8QCGDTJM.mLwjr.cn
    http://6EP58iMc.mLwjr.cn
    http://Gghpne9W.mLwjr.cn
    http://AJWOSPmZ.mLwjr.cn
    http://sXyROzob.mLwjr.cn
    http://q111Txg0.mLwjr.cn
    http://aTzUkU1L.mLwjr.cn
    http://02FfYUc5.mLwjr.cn
    http://3y4JxZzW.mLwjr.cn
    http://LKNTIefF.mLwjr.cn
    http://www.dtcms.com/a/386821.html

    相关文章:

  • 【Linux系统】深入理解线程,互斥及其原理
  • 1. C++ 中的 C
  • 探讨基于国产化架构的非结构化数据管理平台建设路径与实践
  • C++11移动语义
  • 代码随想录第14天| 翻转、对称与深度
  • 算法改进篇 | 改进 YOLOv12 的水面垃圾检测方法
  • 一个我自己研发的支持k-th路径查询的数据结构-owl tree
  • 首款“MODA”游戏《秘境战盟》将在Steam 新品节中开放公开试玩
  • ε-δ语言(Epsilon–Delta 语言)
  • QCA9882 Module with IPQ4019 Mainboard High-Performance Mesh Solution
  • xv6实验:Ubuntu2004 WSL2实验环境配置(包括git clone网络问题解决方法)
  • ICE-Interactive Connectivity Establishment-交互式连接建立
  • 【代码随想录day 28】 力扣 45.跳跃游戏 II
  • IP核的底层封装
  • 4.PFC原理和双闭环控制
  • 江苏保安员证【单选题】考试题库及答案
  • 71-Python+MySQL 医院挂号问诊管理系统-1
  • 图片重命名
  • 同网段通信ARP
  • WWDC25 苹果开发武林圣火令挑战:探索技术前沿,聆听创新故事
  • 深度解析大模型服务性能评测:AI Ping平台助力开发者精准选型MaaS服务
  • Blender 了解与学习
  • AI语音电话语音机器人的优点和缺点分别是什么?
  • 【阿里云PAI平台】 如何在Dify调用阿里云模型在线服务 (EAS)
  • 省钱自学版一次过阿里云ACP!!!
  • 建立了 abc 联合索引,where a = ? and b = ? order by c 能命中索引吗?
  • 携程线下面试总结
  • 【数据工程】9. Web Scraping 与 Web API
  • Vue3 emit和provide
  • linux C 语言开发 (十二) 进程间通讯--消息队列