【C++】C++11 篇二
-
- 前言
-
- 移动构造函数
- 移动赋值运算符重载
- [类成员变量初始化 (缺省值出自C++11](#类成员变量初始化 (缺省值出自C++11)
- 强制生成默认函数的关键字default:
- 禁止生成默认函数的关键字delete:
- 继承和多态中的final与override关键字(出自C++11
- 可变参数模板
- STL容器中的empalce相关接口函数
- lambda表达式
-
- 实例
- lambda表达式语法
-
- 实例:
- [lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }](#lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement })
- 捕获列表说明
- 函数对象(仿函数)与lambda表达式
前言
C++98的6个默认成员函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值重载
- 取地址重载 //用处不大
- const 取地址重载 //用处不大
C++111 新增了两个:移动构造函数 和移动赋值运算符重载
移动构造函数
如果没有自己实现移动构造函数,且析构函数 、拷贝构造、拷贝赋值重载都没有实现 。
那么编译器会自动生成一个默认移动构造。
默认生成的移动构造函数,对于内置类型成员 会执行逐成员按字节拷贝,
自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造
移动赋值运算符重载
(默认移动赋值跟上面移动构造完全类似,把移动构造函数替换为移动赋值运算符重载即可)
类成员变量初始化 (缺省值出自C++11
C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化
强制生成默认函数的关键字default:
提供了拷贝构造,就不会生成移动构造了,使用default关键字强制生成
cpp
Person(Person&& p) = default;
禁止生成默认函数的关键字delete:
cpp
Person(Person&& p) = delete;
继承和多态中的final与override关键字(出自C++11
可变参数模板
// Args是一个模板参数包 ,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数
//C语言的printf scanf函数的参数就是可变的
//语法不支持使用args[i]这样方式获取可变参数
csharp。
template <class ...Args>
void ShowList(Args... args)
{}
递归函数方式展开参数包
cpp
// 递归终止函数
template <class T>
void ShowList(const T& t)
{
cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value <<" ";
ShowList(args...);
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
逗号表达式展开参数包
//由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数 ,再得到逗号表达式的结果0
//通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成((printarg(arg1),0),
(printarg(arg2),0), (printarg(arg3),0), etc... ),最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。
cpp
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
STL容器中的empalce相关接口函数
template <class... Args>
void emplace_back (Args&&... args);
emplace系列的接口,支持模板的可变参数,并且万能引用 。那么相对insert和
emplace系列接口的优势到底在哪里呢?
cpp
int main()
{
std::list< std::pair<int, char> > mylist;
// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
mylist.emplace_back(10, 'a');//避免了临时对象的创建和拷贝 / 移动,效率更高
mylist.emplace_back(make_pair(30, 'c'));//相当于退化为了 push_back() 的效果。
//:push_back() 必须先有一个完整的对象(可能是临时对象),再将其放入容器,比直接构造多一次拷贝 / 移动操作(在优化前)。
mylist.push_back(make_pair(40, 'd'));
mylist.push_back({ 50, 'e' });//只是用初始化列表简化了对象的创建,仍会产生临时对象(调用构造函数)
for (auto e : mylist)
cout << e.first << ":" << e.second << endl;
return 0;
}
lambda表达式
实例
cpp
#include <algorithm>
#include <functional>
int main()
{
int array[] = {4,1,8,5,3,7,0,9,2,6};
// 默认升序
std::sort(array, array+sizeof(array)/sizeof(array[0]));
// 降序greater<int>()依赖#include <functional>
std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
return 0;
}
待排序元素为自定义类型 ,需要用户定义排序时的比较规则,仿函数
cpp
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());
}
lambda表达式(匿名函数),其实底层就是仿函数
cpp
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price < g2._price; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price > g2._price; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._evaluate < g2._evaluate; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._evaluate > g2._evaluate; });
}
lambda表达式语法
实例:
cpp
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[]{};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=]{return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c){b = a + c; };
fun1(10)
cout<<a<<" "<<b<<endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int{return b += a+ c; };
cout<<fun2(10)<<endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
-
capture-list\] : **捕捉列表** ,编译器根据\[\]来判断接下来的代码是否为lambda函数,**捕捉上下文中的变量供lambda 函数使用。**
连同()一起省略** - mutable :默认情况下,lambda函数总是一个const函数 ,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
- returntype:返回值类型 。可省略,由编译器对返回类型进行推导。
- {statement}:函数体 。在该函数体内,可以使用其参数外,还可使用捕获
到的变量。
注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分 ,而捕捉列表和函数体可以为
空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情
捕获列表说明
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
var\]:表示值传递方式捕捉变量var
\[=\]:表示值传递方式捕获**所有父作用域** 中的变量\*\*(包括this)\*\*
\[\&var\]:表示引用传递捕捉变量var
\[\&\]:表示引用传递捕捉**所有父作用域** 中的变量(包括this)
\[this\]:表示**值传递方式捕捉当前的this指针**
> 注意:
>
> a. **父作用域指包含lambda函数的语句块**
>
> b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
>
> 比如:\[=,\&a, \&b\]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
>
> \[\&,a,this\]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
>
> c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
>
> 比如:\[=, a\]:=已经以值传递方式捕捉了所有变量,捕捉a重复
>
> d. **在块作用域以外的lambda函数捕捉列表必须为空** 。
>
> e. 在块作用域中的lambda函数**仅能捕捉父作用域中局部变量** ,捕捉任何非此作用域或者非局部变量都会导致编译报错。
>
> f. **lambda表达式之间不能相互赋值,类型不同,其类型构成含有uuid,通用唯一标识符**
```cpp
void (*PF)();
int main()
{
auto f1 = []{cout << "hello world" << endl; };
auto f2 = []{cout << "hello world" << endl; };
//f1 = f2; //类型不同,不可赋值
// 允许使用一个lambda表达式拷贝构造一个新的副本
auto f3(f2);//默认包含 operator() 重载(用于调用 Lambda 逻辑)
f3();
// 可以将lambda表达式赋值给相同类型的函数指针
PF = f2;
PF();
return 0;
}
```
> 小结
>
> 1. Lambda 表达式会被编译器隐式转换为一个**匿名的函数对象类型**
> 2. 是编译器自动生成的、**唯一的类类型**
> 3. **默认包含 operator() 重载**(用于调用 Lambda 逻辑)
> 4. 对于**没有捕获变量的 Lambda,可隐式转换为函数指针**;有捕获变量的 Lambda 则不能
##### 函数对象(仿函数)与lambda表达式
函数对象,又称为仿函数,即可以像函数一样使用的对象,就是**在类中重载了operator()运算符** 的 **类对象**
```cpp
#define _CRT_SECURE_NO_WARNINGS
#include