这里写目录标题
- [<font color="FF00FF"> 1. C++11中的{}](# 1. C++11中的{})
- [<font color="FF00FF"> 2. C++11中的std::initializer_list](# 2. C++11中的std::initializer_list)
- [<font color="FF00FF">3. 左值和右值](#3. 左值和右值)
- [<font color="FF00FF">4. 左值引用和右值引用](#4. 左值引用和右值引用)
- [<font color="FF00FF">5. 引用延长生命周期](#5. 引用延长生命周期)
- [<font color="FF00FF">6. 左值和右值的参数匹配](#6. 左值和右值的参数匹配)
- [<font color="FF00FF">7. 移动构造和移动赋值](#7. 移动构造和移动赋值)
- [<font color="FF00FF">8. 引用折叠](#8. 引用折叠)
- [<font color="FF00FF">完美转发](#完美转发)
- [<font color="FF00FF"> 10. lambda](# 10. lambda)
- [<font color="FF00FF">11. lambda的应用](#11. lambda的应用)
- [<font color="FF00FF">12. 捕捉列表](#12. 捕捉列表)
-
- [<font color="FF00FF">3.1 隐式值捕捉](#3.1 隐式值捕捉)
- [<font color="FF00FF">3.2 隐式引用捕捉](#3.2 隐式引用捕捉)
- [<font color="FF00FF">3.3 混合捕捉](#3.3 混合捕捉)
- [<font color="FF00FF">3.4 lambda的原理](#3.4 lambda的原理)
- [<font color="FF00FF">13. 可变参数模板](#13. 可变参数模板)
- [<font color="FF00FF">14. emplace](#14. emplace)
- [<font color="FF00FF">15. 默认的移动构造和移动赋值](#15. 默认的移动构造和移动赋值)
- [<font color="FF00FF">16. defult和delete](#16. defult和delete)
- [<font color="FF00FF">17. functional](#17. functional)
- [<font color="FF00FF">18. bind](#18. bind)
1. C++11中的{}
-
C++11支持⼀切对象皆可用{}初始化
-
内置类型和自定义类型都支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化了以后变成直接构造
-
{}初始化的过程中,可以省略掉=
-
C++11列表初始化的本意是想实现⼀个大统⼀的初始化方式,其次他在有些场景下带来的不少便利,如容器push/inset多参数构造的对象时,{}初始化会很方便

2. C++11中的std::initializer_list
-
C++11库中提出了⼀个std::initializer_list的类,这个类的本质是底层开⼀个数组,将数据拷贝过来,std::initializer_list内部有两个指针分别指向数组的开始和结束
-
std::initializer_list支持迭代器遍历

3. 左值和右值
- 左值可以出现赋值符号的左边,也可以出现在赋值符号右边
- 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边
- 左值可以取地址,右值不能,最重要的区别

4. 左值引用和右值引用
- 左值引用不能直接引用右值,但是const左值引用可以引用右值
- 右值引用不能直接引用左值,但是右值引用可以引用(左值)move

5. 引用延长生命周期
右值引用可用于为临时对象延长生命周期,const的左值引用也能延长临时对象生存期

6. 左值和右值的参数匹配

7. 移动构造和移动赋值
- 编译器不优化的情况时时是两次拷贝构造,在vs2019下只有一次拷贝构造


在没有编译器优化的情况下,移动构造和移动赋值效率很高,因为只是交换指针

在vs2022会优化成引用的形式,直接修改ret

右值引用属性是左值,也就是z引用右值,属性是左值
8. 引用折叠
只要有一个左值引用就是左值引用,右值引用可以是左值,也可以是右值
c
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n; // r1 的类型是 int&
lref&& r2 = n; // r2 的类型是 int&
rref& r3 = n; // r3 的类型是 int&
rref&& r4 = 1; // r4 的类型是 int&&
c
template<class T>
void Function(T&& t)
{
int a = 0;
T x = a;
//x++;
cout << &a << endl;
cout << &x << endl << endl;
}
int main()
{
// 10是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(10); // 右值
int a;
// a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t)
Function(a); // 左值
// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(std::move(a)); // 右值
const int b = 8;
// b是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int& t)
// 所以Function内部会编译报错,x不能++
Function(b); // const 左值
// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&& t)
// 所以Function内部会编译报错,x不能++
Function(std::move(b)); // const 右值
return 0;
}
完美转发

完美转发是左值的属性不变,把右值的属性变成左值,所以只有forward时才会左值调用左值的函数,右值调用右值的函数
10. lambda

这就是一个最简单的 lambda 表达式,其中 = 右边的整体看作是一个匿名对象,然后auto自动推导类型,调用add(1,2),就可以实现一个加法函数的工作
1.1 使用 lambda 有四个规定
- 捕捉为空也不能省略:[ ] 不能省略
- 参数为空可以省略
- 返回值可以省略,可以通过返回对象自动推导:->int 可以省略
- 函数体不能省略 :{ }里面的内容不能省略

这个代码就是只有[ ]和{ }里面的内容,返回值和参数被省略
11. lambda的应用
#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
这里可以通过仿函数来进行商品的排序,但是按照多种方式排序就要写多个仿函数,那有没有更好的办法呢?
lambda可以很好的解决问题

lambda可以较为简单的写,一目了然,比如按照价格排序,或者按照评价排序,直接在sort内部就可以清楚的知道,我们在做什么
12. 捕捉列表

一般不在全局定义lambda,一般在局部使用

捕捉多个变量用逗号进行分格,值捕捉的变量不能修改,只能读,引用捕捉的变量才可以修改,如果不在捕捉列表里的变量不可以读,所以修改更不可能了,全局变量在任何地方都可以读写,所以全局变量可以使用
3.1 隐式值捕捉
只能捕捉当前函数内部定义的所有变量,但是不可以修改,因为是传值捕捉,像是被const修饰一样

3.2 隐式引用捕捉
只能捕捉当前函数内部定义的所有变量,但是可以修改

3.3 混合捕捉
混合捕捉时,第⼀个元素必须是&或=
并且&混合捕捉时,后面的捕捉变量必须是值捕捉
同理=混合捕捉时,后面的捕捉变量必须是引用捕捉。

这个捕捉的意思就是只有当前函数内部定义的变量a和b传值捕捉,其它的变量都是引用捕捉,其实{ }里面用了哪些变量,就捕捉哪些变量,没用的其实不会捕捉,因为有成本


这里的()必须加,然后mutable只是去掉了const属性,可以修改变量了,但是它不会影响外部的值,就相当于函数的传值传参,并不会改变实参的值,这里就是这样
3.4 lambda的原理
lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,而lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会生成⼀个对应的仿函数的类。
仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传那些对象。
c
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);
r2(10000, 2);
auto func1 = [] {
cout << "hello world" << endl;
};
func1();
return 0;
}
捕捉列表的rate,可以看到作为lambda_1类构造函数的参数传递了,这样要拿去初始化成员变量
c
// 下面operator()中才能使用
00D8295C lea eax,[rate]
00D8295F push eax
00D82960 lea ecx,[r2]
00D82963 call `main'::`2'::<lambda_1>::<lambda_1> (0D81F80h)
// 函数对象
Rate r1(rate);
00D82968 sub esp,8
00D8296B movsd xmm0,mmword ptr [rate]
00D82970 movsd mmword ptr [esp],xmm0
00D82975 lea ecx,[r1]
00D82978 call Rate::Rate (0D81438h)
r1(10000, 2);
00D8297D push 2
00D8297F sub esp,8
00D82982 movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D8298A movsd mmword ptr [esp],xmm0
00D8298F lea ecx,[r1]
00D82992 call Rate::operator() (0D81212h)
// 汇编层可以看到r2 lambda对象调⽤本质还是调⽤operator(),类型是lambda_1,这个类型名
// 的规则是编译器⾃⼰定制的,保证不同的lambda不冲突
r2(10000, 2);
00D82999 push 2
00D8299B sub esp,8
00D8299E movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D829A6 movsd mmword ptr [esp],xmm0
00D829AB lea ecx,[r2]
00D829AE call `main'::`2'::<lambda_1>::operator() (0D824C0h)
所以lambda底层还是调用了operator(),就是仿函数
注意:operator()是运算符重载,拥有operator的类是仿函数
13. 可变参数模板

编译时可变参数模板会根据类型推导,形成如下这些函数
c
void Print();
void Print(int&& arg1);
void Print(int&& arg1, string&& arg2);
void Print(double&& arg1, string&& arg2, double& arg3);
相当于实现了跟下面一样的效果
c
void Print();
template <class T1>
void Print(T1&& arg1);
template <class T1, class T2>
void Print(T1&& arg1, T2&& arg2);
template <class T1, class T2, class T3>
void Print(T1&& arg1, T2&& arg2, T3&& arg3);
调用ShowList,参数包的第⼀个传给x,剩下N-1传给第二个参数包

原理:

c
template <class T>
const T& GetArg(const T& x)
{
cout << x << " ";
return x;
}
template <class ...Args>
void Arguments(Args... args)
{}
template <class ...Args>
void Print(Args... args)
{
// 注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments
Arguments(GetArg(args)...);
}
int main()
{
Print(1, string("xxxxx"), 2.2);
return 0;
}
原理
c
void Print(int x, string y, double z)
{
Arguments(GetArg(x), GetArg(y), GetArg(z));
}
14. emplace

这是两者的区别


value_type就是T就是string,这里const char*先隐式类型转换构造出string临时对象,这个过程调用的是string的构造函数,再移动构造给链表里的string val形成结点,只不过在进行链表的移动构造时要先调用string的移动构造,再继续执行list的移动构造,因为链表里存的是自定义string类型


而这里模板直接推导插入的值为const char*,然后直接调用链表的构造函数,然后这个字符串直接隐式类型转换,也就是一次构造直接形成string,然后再继续进行执行list的构造函数
15. 默认的移动构造和移动赋值
-
有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意⼀个。那么编译器会自动生成⼀个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造
-
拷贝赋值和拷贝构造条件一样
-
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
16. defult和delete
如果你写了拷贝构造,编译器就不会生成移动构造了,而default就是强制生成,而delete是禁制生成函数,这里注释的就是禁制生成拷贝构造

17. functional
c
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()
{
// 包装各种可调⽤对象
function<int(int, int)> f1 = f;
function<int(int, int)> f2 = Functor();
function<int(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;
// 包装静态成员函数
// 成员函数要指定类域并且前面加&才能获取地址
function<int(int, int)> f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
// 包装普通成员函数
// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.1, 1.1) << endl;
function<double(Plus, double, double)> f6 = &Plus::plusd;
cout << f6(pd, 1.1, 1.1) << endl;
cout << f6(Plus(), 1.1, 1.1) << endl;
function<double(Plus&&, double, double)> f7 = &Plus::plusd;
cout << f7(move(pd), 1.1, 1.1) << endl;
cout << f7(Plus(), 1.1, 1.1) << endl;
return 0;
}
18. bind
c
int Sub(int a, int b)
{
return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
return (a - b - c) * 10;
}
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;
// bind 本质返回的⼀个仿函数对象
// 调整参数顺序(不常⽤)
// _1代表第⼀个实参
// _2代表第⼆个实参
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl;
// 调整参数个数 (常⽤)
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;
auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl;
// 成员函数对象进⾏绑死,就不需要每次都传递了
// bind⼀般⽤于,绑死⼀些固定参数
function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;
return 0;