【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指针。
- 如果包装器第一个参数位置是类型名的指针,那么传参时就只能传类对象的地址。
- 如果第一个参数位置就是单纯的类型名,那么可以直接传对象,包括匿名对象。
- 如果第一个参数位置是类型名的左值引用,那么就只能传左值,不包括匿名对象。
- 如果第一个参数位置是类型名的右值引用,那么就只能传右值,包括匿名对象。
- 还有静态成员函数,静态成员函数因为没有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遍历,这个在容器部分也讲过了。
总结
好啦,以上就是本文的全部内容了,感谢支持!