目录
一、列表初始化
1.1 {}初始化
1.2 initializer_list
二、变量类型推导
2.1 auto
2.2 decltype
三、STL中一些变化
3.1 新增容器
四、lambda表达式
4.1 C++98中的一个例子
4.2 lambda表达式
4.3 函数对象与lambda表达式
五、包装器
5.1 function包装器
5.2 function 的使用方法
5.3 bind
一、列表初始化
1.1 {}初始化
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自
定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
int a[2]{ 1,2 };
int b{ 3 };
vector<int> c{ 4,5,6,7,8 };
1.2 initializer_list
我们能使用{}初始化,是因为initializer_list的原因,下面我们来了解一下它。
它是一个新的C++的容器,一个类模板,是一个用于初始化的工具。
举一个例子,你如何用下面的数组来初始化vector呢?
int arr[5] = { 1, 2, 3, 4 };
vector<int> v;
你可能会用下面的代码来初始化
for (int i = 0; i < 5; i++)
{
v.push_back(arr[i]);
}
但是这好像太麻烦了吧。C++11后,STL的容器都增加了新的构造函数,可以通过initializer_list来初始化容器。
vector<int> v({ 1, 2, 3, 4, 5 });
这个写法,是单参数的类型转化,因为{ 1, 2, 3, 4, 5 }整体就是一个initializer_list类型的参数
同样的,我们也可以用它来初始化map
map<string, string> m = { {"apple","苹果"}, {"banana","香蕉"}, {"pear", "梨"} };
最外层的{ }就是一个initializer_list。
它的接口也很简单
(constructor) | 构造空 initializer_list |
size | 返回列表大小 |
begin | 返回迭代器的开头 |
end | 返回迭代器的最后 |
我们也可以看出来
initializer_list的本质上是一个通过迭代器访问数组的容器。当其它容器通过initializer_list构造自己时,其实就是通过迭代器遍历那个存储了节点的数组,然后把数组元素一个一个插入。
换而言之,下面这两种写法是一样的。
initializer_list<int> lt = { 1, 2, 3, 4 };
list<int> l1({ 1, 2, 3, 4 });
list<int> l2(lt.begin(), lt.end());
二、变量类型推导
2.1 auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局
部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将
其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初
始化值的类型。
auto i = 1;//整型
auto d = 3.14;//浮点型
auto p = &i;//指针
vector<int> v;
auto it = v.begin();//迭代器
2.2 decltype
decltype可以检测一个变量的类型,并且拿这个类型去声明新的类型。比如下面这个
int i = 0;
decltype(i) x = 5;
decltype(i)检测出i的类型为int,于是decltype(i)整体就变成int,从而定义出一个新的变量x。
三、STL中一些变化
3.1 新增容器
用橘色圈起来是C++11中的一些几个新容器。但最有用的是unordered_map、unordered_set。
四、lambda表达式
4.1 C++98中的一个例子
函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的
类对象。
在C++98中,如果想要对一个结构体进行排序,就需要用户自己写仿函数。
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;
}
};
随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,
都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,
这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。
4.2 lambda表达式
lambda表达式书写格式:
[capture_list] (parameters) mutable -> return_type {statement}
我们来看看各部分是做什么的
[capture_list]
:捕捉列表。编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用(parameters)
:参数列表。普通函数的参数列表一致。如果不需要参数传递,则可以
连同()一起省略mutable
:一个关键字。mutable可以取消其常量性。使用该修饰符时,参数列表不可省略-> return_type
:返回值类型。用追踪返回类型形式声明函数的返回值类型,返回值类型明确情况下,可省略,由编译器对返回类型进行推导。没有返回值时此部分也可省略{statement}
: 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量
举一个例子,下面是一个完整的lambda表达式。
auto add = [](int a, int b)mutable -> int { return a + b; };
当然我们也可以将其省略成下面这个式子
auto add = [](int a, int b) { return a + b; };
那lambda表达式有什么作用呢?答案是:lambda会返回一个仿函数对象
比如我们上面写的例子,其实add就是一个仿函数对象了,我们可以直接按照调用函数的方式来调用这个仿函数:add(1, 2);。但是要注意, lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,因此必须使用auto来接受这个仿函数对象。
下面我们再来详细地讲一讲lambda表达式最前面的[]的作用。
[]描述了上下文中那些数据可以被lambda使用,以及使用的方式是传值还是传引用。
- [var]:表示值传递方式捕捉变量var
- [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]:表示值传递方式捕捉当前的this指针
举几个例子解释一下。
int x = 1;
int y = 2;/*如果直接通过变量名捕获,此时是传值调用,修改函数体内部的变量,不会影响父作用域的变量*/
auto add = [x, y] {return x + y; };
/*通过直接传值捕获的变量,自带const属性,不允许修改。此时代码就会报错*/
/*auto add = [x, y]
{
x += 5;
y += 5;
};*//*mutable可以让被捕获的参数可以修改。需要注意的是如果使用了mutable,()不可省略*/
auto add = [x, y] () mutable
{
x += 5;
y += 5;
};
/*以传引用的方式来捕获变量,此时修改函数内部的x和y,就是在修改父作用域的x和y。如果使用了传引用捕获变量,就算没有mutable也可以修改参数*/auto add = [&x, &y]
{
x += 5;
y += 5;
};
/*[=]是以传值的形式捕获父作用域所有变量,[&]是以传引用的形式捕获父作用域所有变量*/
auto add = [=]
{
return x + y;
};
auto add = [&]
{
return x + y;
};/*
我们还可以把传值和传引用混合使用,让部分参数传参,部分参数传引用。
[x, &y]:以传值的形式捕获x,以传引用的形式捕获y
[=, &x]:以传值的形式捕获父作用域所有变量,以传引用的形式捕获x
[&, x]:以传值的形式捕获x,以传引用的形式捕获父作用域所有变量*/
有了lambda表达式之后,我们就可以改变一下最开始的排序了
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; });
4.3 函数对象与lambda表达式
从使用方式上来看,函数对象与lambda表达式完全一样。
函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可
以直接将该变量捕获到。
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
五、包装器
当我们在寄快递的时候,快递会被包装,这样我们就可以统一的在上面贴上快递信息,随后以统一的形式管理所有快递。包装器也是如此,包装器可以将具有相似属性的东西包装起来成为一个整体。
5.1 function包装器
function包装器,也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
那么我们在什么时候会用到包装器呢?我们一步步来看
我们首先回忆一下我们知道的可调用对象
- 函数指针
- 仿函数实例化出的对象
- lambda表达式
但是它们都有各自的缺点
函数指针:类型复杂,不好用
仿函数实例化出的对象:哪怕参数返回值都相同,仿函数之间的类型也不同
lambda表达式:类型是随机的,必须用auto接收
可以看到,这三者都有类型方面的大问题,我们也没有一种方式可以把所有参数类型和返回值类型相同的函数,统一的管理起来,让它们都变成一个类型?这个时候就用到包装器了。
std::function在头文件<functional>
// 类模板原型如下
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
function包装器,只要是返回值和参数列表都相同的可调用对象,经过这一层封装,都会变成相同的类型。
举一个例子
double func(double x)
{
return x / 2;
}struct Functor
{
double operator()(double x)
{
return x / 3;
}
};int main()
{
auto lambadaFunc = [](double d) {return d / 4; };/*func,Functor,lambadaFunc 。它们的返回值都是double,参数类型也是double,因此可以经过包装器包装为function<double<double>>*/
/*现在三者的类型就都是function<double(double)>*/
function<double(double)> func1 = func;
function<double(double)> func2 = Functor();
function<double(double)> func3 = lambadaFunc;return 0;
}
5.2 function 的使用方法
// 使用方法如下:
#include <functional>
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}};
int main()
{
// 函数名(函数指针)
std::function<int(int, int)> func1 = f;
cout << func1(1, 2) << endl;
// 函数对象
std::function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl;
// lamber表达式
std::function<int(int, int)> func3 = [](const int a, const int b)
{return a + b; };
cout << func3(1, 2) << endl;
// 类的成员函数
std::function<int(int, int)> func4 = &Plus::plusi;
cout << func4(1, 2) << endl;
std::function<double(Plus, double, double)> func5 = &Plus::plusd;
cout << func5(Plus(), 1.1, 2.2) << endl;
return 0;
}
5.3 bind
bind函数
- 是一个函数模板
- 其接收多个参数,第一个参数为可调用对象,后续参数为该可调用对象的参数
- 其主要有两个功能:改变参数顺序,给指定参数绑定固定值
C++11后新增一个命名空间域placeholders,其内部会存储很多变量,这些变量用于函数的传参,变量的名字为_x表示第x个参数。
举个例子
int sub(int a, int b)
{
return a - b;
}int main()
{/*sub这个参数是一个可调用对象。
placeholders::_2表示第二个参数,placeholders::_1表示第一个参数*/
auto f1 = bind(sub, placeholders::_2, placeholders::_1);/*f1最后拿到了这个bind封装的函数,那么f1(3, 5)执行的并不是3 - 5,而是5 - 3*/
f1(3, 5);
return 0;
}
int sub(int a, int b)
{
return a - b;
}int main()
{/*第一个参数为可调用对象sub,第二个参数是一个固定值3.14,参数a都固定为3.14*/
auto f2 = bind(sub, 3.14, placeholders::_1);f2(10);//3.14 - 10
return 0;
}
bind 函数还可以用来处理函数的返回值
int add(int x, int y) {
return x + y;
}int main() {
// 绑定 add 函数,并将返回值乘以 2
auto doubleAdd = bind([](int result) { return result * 2; }, add(placeholders::_1,placeholders::_2));
int result = doubleAdd(3, 4);
cout << result << endl; // 输出 14
return 0;
}