1. 统一的列表初始化
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
int x1 = 1;
// 能看懂,但不建议使用
int x2 = { 2 };
int x3 { 2 };
Date d1(2022, 11, 22);// ->调用构造函数
// 能看懂,但不建议使用
Date d2 = {2022, 11, 11}; // ->调用构造函数
Date d3{ 2022, 11, 11 };// ->调用构造函数
vector<int> v1 = { 1, 2, 3, 4, 5, 6 };
vector<int> v2 { 1, 2, 3, 4, 5, 6 };
list<int> lt1 = { 1, 2, 3, 4, 5, 6 };
list<int> lt2{ 1, 2, 3, 4, 5, 6 };
auto x = { 1, 2, 3, 4, 5, 6 };
cout << typeid(x).name() << endl;
return 0;
}
- C++11中增大了{ }的使用范围,要求能看懂,但是不建议使用
1.1 {}在自定义类型中的应用
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 11, 22);
Date d2 = {2022, 11, 11};
Date d3{ 2022, 11, 11 };
vector<Date> v3 = {d1, d2, d3};
// C++11中{}的真正用途
vector<Date> v4 = { { 2022, 1, 1 }, {2022, 11, 11} };
map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } };
// 赋值重载
// 这里的auto无法自动推导,所以自己显示类型
initializer_list<pair<const string, string>> kvil = { { "left", "左边" }, { "left", "左边" } };
dict = kvil;
return 0;
}
- vector<Date> v4 = { { 2022, 1, 1 }, {2022, 11, 11} };
1.2 initializer_list的引入
- C++11增加了 initializer_list,以及对vector和list等容器的更新,所以才支持上面的{}用法
1.3 小结
- C++11以后一切对象都可以用列表初始化,但是建议普通对象还是用以前的方式初始化,
- 容器如果有需求的话就可以用列表初始化
2. decltype的引入
#include <iostream>
using namespace std;
int main()
{
int x = 10;
// typeid拿到只是类型的字符串,不能用这个再去定义对象什么的
//typeid(x).name() y = 20;
decltype(x) y1 = 20.22;
auto y2 = 20.22;
cout << y1 << endl;
cout << y2 << endl;
return 0;
}
- typeid拿到只是类型的字符串,不能用这个再去定义对象什么的
- decltype不仅仅可以拿到变量的类型,还可以去定义对象
3. C++11新增的容器
- unordered_map和unordered_set比较有用
3.1 容器array
#include <iostream>
#include <array>
using namespace std;
int main()
{
const size_t N = 100;
int a1[N];
// C语言数组越界检查,越界读基本检查不出来,越界写是抽查
a1[N];
//a1[N] = 1;
//a1[N + 5] = 1;
// 越界读写都可以被检查出来
array<int, 1> a2;
a2[N];
a2[N] = 1;
a2[N + 5] = 1;
return 0;
}
- array容器只要是越界就一定能被检查出来
- C语言数组越界检查,越界读基本检查不出来,越界写是抽查
array实际使用情况
- array用得很少,一方面大家用c数组用惯了
- 用array不如用vector + resize去替代c数组
4. 左值引用& 和 右值引用&&
#include <iostream>
#include <utility>
using namespace std;
int main()
{
// 左值: 能取地址
int* p = new int(0);
int b = 1;
const int c = 2;
// 对左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
// 右值:不能取地址
10;
1.1 + 2.2;
// 对右值的右值引用
int&& rr1 = 10;
double&& rr2 = 1.1 + 2.2;
// 对右值的左值引用
// double& r1 = 1.1 + 2.2;// error
const double& r1 = 1.1 + 2.2;
// 对左值的右值引用
//int&& rr5 = b;// error
int&& rr5 = move(b);
return 0;
}
- 左值: 能取地址
- 右值: 不能取地址,如:临时对象,const的不能被修改的对象
- 如果对右值使用左值引用,需要加上const
- 如果对左值使用右值引用,需要是使用move函数
4.1 左值引用可以解决的问题
- 做参数,a. 减少拷贝 b.做输出型参数
- 做返回值,a.减少拷贝, 提高效率 b.引用返回,可以修改返回对象(比如: operator[])
4.2 左值引用无法解决的问题
- 引用返回的前提是返回值出了作用域之后还在,
- 但是无法解决string中的to_string的返回值,以及有些函数返回值是二维数组的问题
4.3 右值引用的实际用途: 移动构造 + 移动赋值
4.3.1 没有移动构造
- 这里的string需要自己写,才能看到结果
- 这里的g++编译器优化的更厉害,这里虽然定义了ret
但是没有使用,所以g++一次都没有拷贝构造 - -fno-elide-constructors可以取消编译器的优化
4.3.2 加上移动构造
- 函数参数的匹配原则是会优先匹配最合适自己的参数,
- to__string(-3456)中的-3456是一个右值,会优先匹配到移动构造,会发生右值引用返回
- 右值:1.内置类型右值-纯右值 2. 自定义类型右值 - 将亡值
- 将亡值就是快要亡了的值,所以它的值就可以直接交换(swap)
- 将拷贝构造变成移动构造,会极大的提高效率
4.3.3 没加移动赋值
- g++编译器优化的很厉害
4.3.4 加上移动赋值
- 移动赋值和移动构造一样,减少了拷贝,提高了效率
4.4 容器插入接口->右值版本
- C++11以后,几乎所有的容器插入接口都提供了右值版本
- 插入过程中,如果传递对象是右值对象,那么就会进行资源转移减少拷贝
4.5 引用折叠
#include <iostream>
#include <utility>
using namespace std;
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
// 万能引用:t既能引用左值,也能引用右值
// 引用折叠
template<typename T>
void PerfectForward(T&& t)
{
Fun(t);
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
- 模板参数的右值引用叫万能引用,T&& t中的t即能引用左值,也能引用右值
- 这里会触发引用折叠,编译器会把它识别成左值
4.5.1 完美转发解决引用折叠问题
- 完美转发:保持t引用对象属性(是编译器能分出左值和右值)
- 它通过std::forward<模板>(参数)实现
5. 新的类功能
5.1 C++中默认成员函数
- 构造 析构 拷贝构造
- 赋值重载 取地址重载 const取地址重载
- 和C++11新增的移动构造,移动赋值
5.2 移动构造 && 移动赋值的注意事项
- 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任 意一个。
- 那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,
- 对于内置类 型成员会执行逐成员按字节拷贝,
- 自定义类型成员,则需要看这个成员是否实现移动构造,
- 如果实现了就调用移动构造,没有实现就调用拷贝构造
- 移动赋值运算符重载同上
5.3 强制生成默认函数的关键字default
5.4 禁止生成默认函数的关键字delete
6. 可变参数模板
#include <iostream>
using namespace std;
// 可变参数的函数模板
template <class ...Args>
void ShowList(Args... args)
{
cout << sizeof...(args) << endl;
}
int main()
{
string str("hello");
ShowList();
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', str);
return 0;
}
- sizeof...(args)// 求模板参数的个数
6.1 使用方法
// 解决只有一个参数的情况
void ShowList()
{
cout << endl;
}
// Args... args代表N个参数包(N >= 0)
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
cout << "ShowList("<<val<<", " << sizeof...(args) << "参数包)" << endl;
ShowList(args...);
}
int main()
{
string str("hello");
ShowList(1, 'A', str);
return 0;
}
- 通过递归调用去使用模板参数
7. emplace_back接口的引入
#include <list>
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
Date& operator=(const Date& d)
{
cout << "Date& operator=(const Date& d))" << endl;
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
list<Date> lt1;
cout << "---------------------------------" << endl;
lt1.push_back(Date(2022, 11, 16));
cout << "---------------------------------" << endl;
lt1.emplace_back(2022, 11, 16);
cout << "---------------------------------" << endl;
return 0;
}
- emplace_back和push_back对内置类型的处理是一样的
- 因为emplace_back可以直接传参数,就只会发生构造,比push_back少一次的拷贝
8. lambda表达式
lambda表达式就像是函数的一种简略写法
8.1 没引入lambda表达式之前
#include <vector>
#include <string>
#include <iostream>
#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());
}
-
在c++11之前要通过不同的标准排序
-
就需要传不同的仿函数才能解决问题
8.2 引入lambda表达式之后
#include <vector>
#include <string>
#include <iostream>
#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 } };
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;
});
}
8.3 lambda表达式语法
[捕捉列表](参数列表)mutable->返回值类型{函数体实现}
-
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时, 参数列表不可省略 (即使参数为空)。
注意:
- 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为 空。
- 因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
- 定义了lambda,还需要调用
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
// 两个数相加的lambda
auto add1 = [](int a, int b)->int{return a + b; };
cout << add1(1, 2) << endl;
// 省略返回值
auto add2 = [](int a, int b){return a + b; };
cout << add2(1, 2) << endl;
// 交换变量的lambda
int x = 0, y = 1;
auto swap1 = [](int& x1, int& x2)->void{int tmp = x1; x1 = x2; x2 = tmp; };
swap1(x, y);
cout << x << ":" << y << endl;
auto swap2 = [](int& x1, int& x2)
{
int tmp = x1;
x1 = x2;
x2 = tmp;
};
swap2(x, y);
cout << x << ":" << y << endl;
return 0;
}
8.4 默认捕捉列表
- 不传参数,默认捕捉的对象不能修改
8.5 捕获列表说明
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
- [var]:表示值传递方式捕捉变量var
- [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]:表示值传递方式捕捉当前的this指针
#include <iostream>
using namespace std;
static int f = 1;// 全局
int ff = 2;// 全局
int func()
{
int a, b, c, d, e;
a = b = c = d = e = 1;
// [=] 全部传值捕捉
auto f1 = [=]() {
cout << a << b << c << d << e << endl;
};
f1();// 调用
//auto f2 = [=, a]() {};// error
// 混合捕捉
auto f2 = [=, &a]() {
a++;
cout << a << b << c << d << e << endl;
};
f2();// 调用
static int x = 0;
if (a)
{
// []也能捕捉全局变量
auto f3 = [&, a]() {
//a++;
b++, c++, d++, e++, x++;
cout << a << b << c << d << e << endl;
f++, ff++;
cout << f << " " << ff << endl;
};
f3();// 调用
}
return 0;
}
int main()
{
func();
不同的栈帧,不可捕捉
//auto f4 = [&, a]() {
// //a++;
// b++, c++, d++, e++, x++;
// cout << a << b << c << d << e << endl;
// f++, ff++;
// cout << f << " " << ff << endl;
//};
return 0;
}
- [&, a]表示除了a是值传递,其他的都是引用传递
- auto f2 = [=, a]() {};
-
捕捉列表不允许变量重复传递 ,否则就会导致编译错误
-
- 在块作用域以外的lambda函数捕捉列表必须为空。
- 不同的栈帧,不可捕捉,比如f4要去捕捉其他栈帧的变量
#include <iostream>
using namespace std;
void (*PF)();
int main()
{
auto f1 = [] {cout << "hello world" << endl; };
auto f2 = [] {cout << "hello world" << endl; };
//f1 = f2// error
// 允许使用一个lambda表达式拷贝构造一个新的副本
auto f3(f2);
f3();
// 可以将lambda表达式赋值个相同类型的函数指针
PF = f2;
PF();
return 0;
}
- lambda表达式之间不能相互赋值
- 但是允许使用一个lambda表达式拷贝构造一个新的副本
- 但是可以将lambda表达式赋值给相同类型的函数指针
8.6 函数对象与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;
};
// lambda_uuid
class lambda_xxxx
{
};
int main()
{
// 函数对象
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);
// 仿函数lambda_uuid
// lambda -> lambda_uuid
auto r2 = [=](double monty, int year)->double{return monty*rate*year; };
r2(10000, 2);
auto r3 = [=](double monty, int year)->double{return monty*rate*year; };
r3(10000, 2);
return 0;
}
- 实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
9. function包装器
一般用来包装lambda表达式
-
Ret: 被调用函数的 返回类型
-
Args…:被调用 函数的形参
#include <iostream>
#include <functional>
using namespace std;
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
// 使用包装器
// 函数指针
function<double(double)> f1 = f;
cout << useF(f1, 11.11) << endl;
// 函数对象
function<double(double)> f2 = Functor();
cout << useF(f2, 11.11) << endl;
// lamber表达式对象
function<double(double)> f3 = [](double d)->double{ return d / 4; };
cout << useF(f3, 11.11) << endl;
// 不使用包装器
// 函数名
cout << useF(f, 11.11) << endl;
// 函数对象
cout << useF(Functor(), 11.11) << endl;
// lamber表达式
cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
return 0;
}
- function就是一种适配器,是 lambda的另一种引用
- C++中的function本质是一个类模板,也是一个包装器。
9.1 案例:逆波兰表达式求值
- 包装器将多个相似的小函数包装在一起
10. bind 绑定
bind绑定一般是配合function包装器 使用的
#include <iostream>
#include <functional>
#include <map>
using namespace std;
int Div(int a, int b)
{
return a / b;
}
int Plus(int a, int b)
{
return a + b;
}
int Mul(int a, int b, double rate)
{
return a * b * rate;
}
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
using namespace placeholders;
int main()
{
// 调整个数, 绑定死固定参数
function<int(int, int)> funcPlus = Plus;
//function<int(Sub, int, int)> funcSub = &Sub::sub;
function<int(int, int)> funcSub = bind(&Sub::sub, Sub(), _1, _2);
function<int(int, int)> funcMul = bind(Mul, _1, _2, 1.5);
map<string, function<int(int, int)>> opFuncMap =
{
{ "+", Plus},
{ "-", bind(&Sub::sub, Sub(), _1, _2)}
};
cout << funcPlus(1, 2) << endl;
cout << funcSub(1, 2) << endl;
cout << funcMul(2, 2) << endl;
cout << opFuncMap["+"](1, 2) << endl;
cout << opFuncMap["-"](1, 2) << endl;
cout << "------------------------" << endl;
int x = 2, y = 10;
cout << Div(x, y) << endl;
// 调整顺序, _1表示第一个参数,_2表示第二个参数
function<int(int, int)> bindFunc2 = bind(Div, _2, _1);
cout << bindFunc2(x, y) << endl;
return 0;
}
可以将bind函数看作是一个通用的
函数适配器
,它接受一个可调用对象,生成一个新的可调用对
象来“适应”原对象的参数列表。
调用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为第二个参数,以此类推。
- 数值n表示生成的可调用对象中参数的位置:
10.1 bind包装器的意义
- 将一个函数的某些参数绑定为固定的值,让我们在调用时可以不用传递某些参数。
- 可以对函数参数的顺序进行灵活调整。