目录
5.1.1包装器包装函数,仿函数对象,lambda表达式,静态成员函数
1.可变参数模板
1.1基本语法及原理
C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函数参数。可变参数模板就是参数类型可变,参数个数可变的模板。
⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class...或typename...指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟...指出接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板⼀样,每个参数实例化时遵循引⽤折叠规则。
可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
template <class ...Args> void Func(Args... args) {}
template <class ...Args> void Func(Args&... args) {}
template <class ...Args> void Func(Args&&... args) {}
可以使⽤sizeof...运算符去计算参数包中参数的个数。
int main()
{
double x = 2.2;
Print(); //包里有0个参数
Print(1); //包里有1个参数
Print(1, string("xxxxx")); //包里有2个参数
Print(1.1, string("xxxxx"), x); //包里有3个参数
return 0;
}
原理1:编译本质会结合引用折叠规则实例化出以下四个函数。
void Print();
void Print(int&& arg1);
void Print(int&& arg1, string&& arg2);
void Print(double&& arg1, string&& arg2, double& arg3);
原理2:在没有可变参数模板时,我们要实现多个函数模板才能支持实例化出上述参数个数不同的函数。可变参数模板是类型泛化加上参数数量变化,使泛型编程更灵活。 可以理解为上述代码是先通过可变参数模板实现出下面一个无参函数和3个函数模板,再通过函数模板实例化出上述四个函数的。
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);
1.2包扩展
对于一个参数包,我们除了能计算它的参数个数,还能做的唯一的事情就是扩展他,当扩展一个包时,我们还要提供用于每个扩展元素的模式,扩展一个包就是将他分解为构成的元素,对每个元素引用该模式,我们通过在模式参数的右边放一个省略号(...)来触发扩展操作。底层的实现细节如下。
//编译时递归推导解析参数
void ShowList()
{
//编译时,该函数是递归推导时解析参数的终止条件,参数包个数为0是,直接匹配这个函数
cout << endl;
}
template<class T, class ...Args>
void ShowList(T x, Args... args)
{
cout << x << " ";
//args是N个参数的参数包
//调用ShowList,参数包的第一个传给x,剩下N-1个传给第二个参数包
ShowList(args...);
}
template<class ...Agrs>
void Print(Agrs... args)
{
ShowList(args...);
}
int main()
{
Print();
Print(1);
Print(1, string("xxxxx"));
Print(1, string("xxxxx"), 2.2);
return 0;
}
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("xxxx"), 2.2);
return 0;
}
本质上可以理解为编译器编译时,将上面的函数模板扩展实例化为下面的函数
void Print(int x, string y, double z)
{
Arguments(GetArg(x), GetArg(y), GetArg(z));
}
1.3emplace系列接口
C++11之后STL容器新增了emplace系列的接口,emplace系列的接口均为模板可变参数,功能上兼容push和insert系列,但是emplace还支持新玩法,假设容器为container<T>,emplace还支持直接插入构造T对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造T对象。emplace系列总体而言是更高效,推荐以后使用emplace系列替代insert和push系列。 这里使用的是C++11语法介绍(1)中提到的string.h。
#include "string.h"
int main()
{
list<xiaoc::string> lt;
xiaoc::string s1("11111111");
xiaoc::string s2("11111111");
//传左值,emplace系列和push_back一样
lt.emplace_back(s1);
cout << "*******************************" << endl;
lt.push_back(s2);
cout << "*******************************" << endl;
//传右值, emplace系列和push_back一样
lt.emplace_back(move(s1));
cout << "*******************************" << endl;
lt.push_back(move(s2));
cout << "*******************************" << endl;
//这里传const char*的对象,将这个对象构成的参数包往下传
//就直接传到构造函数,直接用参数包构造string
lt.emplace_back("11111111");
cout << "*******************************" << endl;
//这里在类实例化的时候就实例出了value的类型
//所以这里走的是隐式类型转换,先构造出一个临时对象,临时对象再走移动构造
lt.push_back("11111111");
cout << "*******************************" << endl;
return 0;
}
#include "string.h"
int main()
{
list<pair<xiaoc::string, int>> lt1;
// 传左值和传右值跟push_back⼀样
// 构造pair + 拷⻉(移动)构造pair到list的节点中data上
pair<xiaoc::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
cout << "*********************************" << endl;
// 跟push_back⼀样
lt1.emplace_back(move(kv));
cout << "*********************************" << endl;
// 直接把构造pair参数包往下传,直接⽤pair参数包构造pair
// 这⾥达到的效果是push_back做不到的
//lt1.emplace_back({ "苹果", 1 }); //不支持
lt1.emplace_back("苹果", 1);
cout << "*********************************" << endl;
//push_back这里是多参数的隐式类型转换
//lt1.push_back("苹果", 1); //不支持
lt1.push_back({ "苹果", 1 });
cout << "*********************************" << endl;
return 0;
}
这里放一份模拟实现含有emplace_back()的list.h以及测试代码,读者可以自行进行调试来检测每个测试走的是哪个函数。
//list.h
#pragma once
namespace xiaoc
{
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode() = default;
//可变参数模板的ListNode的构造
//参数包传到这里,进行构造
template<class... Args>
ListNode(Args&&... args)
:_next(nullptr)
,_prev(nullptr)
,_data(std::forward<Args>(args)...)
{}
};
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
ListIterator(Node* node)
:_node(node)
{}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self & it)
{
return _node != it._node;
}
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
void empty_init()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
//这里是用T类型做参数 -- T在类模板实例化是就知道类型了
//用于支持隐式类型转换
// lt1.push_back({ "苹果", 1 }),走这个支持隐式类型转换成pair对象
//但是只有这个不行,右值引用不能引用左值
void push_back(T&& x)
{
insert(end(), forward<T>(x));
}
//万能引用 -- 传左值调用左值版本的insert(),传右值调用右值版本的insert()
//传左值实例化为void push_back(string& x)
template<class X>
void push_back(X&& x)
{
insert(end(), forward<X>(x));
}
//insert(it, { "苹果", 1 }),走这个支持隐式类型转换构造pair对象
iterator insert(iterator pos, T&& x)
{
Node* cur = pos._node;
Node* newnode = new Node(move(x));
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
//可变参数模板 -- 调用可变参数版本的insert()
template<class... Args>
void emplace_back(Args&& ...args)
{
insert(end(), std::forward<Args>(args)...);
}
//可变参数的万能引用版本
//这里接收传过来的参数包,在new的时候调用构造函数直接构造节点
template<class... Args>
iterator insert(iterator pos, Args&&... args)
{
Node* cur = pos._node;
Node* newnode = new Node(std::forward<Args>(args)...);
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
private:
Node* _head;
};
}
#include "list.h"
#include "string.h"
int main()
{
xiaoc::list<xiaoc::string> lt;
xiaoc::string s1("11111111");
xiaoc::string s2("11111111");
lt.emplace_back(s1);
cout << "*******************************" << endl;
lt.push_back(s2);
cout << "*******************************" << endl;
lt.emplace_back(move(s1));
cout << "*******************************" << endl;
lt.push_back(move(s2));
cout << "*******************************" << endl;
lt.emplace_back("11111111");
cout << "*******************************" << endl;
lt.push_back("11111111");
cout << "*******************************" << endl;
xiaoc::list<pair<xiaoc::string, int>> lt1;
pair<xiaoc::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
cout << "*********************************" << endl;
lt1.emplace_back(move(kv));
cout << "*********************************" << endl;
//lt1.emplace_back({ "苹果", 1 }); //不支持
lt1.emplace_back("苹果", 1);
cout << "*********************************" << endl;
//lt1.push_back("苹果", 1); //不支持
lt1.push_back({ "苹果", 1 });
cout << "*********************************" << endl;
pair<xiaoc::string, int> p1 = { "苹果", 1 };
auto it = lt1.begin();
//走iterator insert(iterator pos, Args&&... args)
lt1.insert(it, p1);
//走iterator insert(iterator pos, T&& x)进行隐式类型转化
lt1.insert(it, { "苹果", 1 });
return 0;
}
2.新的类功能
2.1默认的移动构造和移动赋值
原来C++类中,有6个默认成员函数:构造函数/析构函数/拷⻉构造函数/拷⻉赋值重载/取地址重载/const 取地址重载,最后重要的是前4个,后两个⽤处不⼤,默认成员函数就是我们不写编译器会⽣成⼀个默认的。C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
如果你没有⾃⼰实现移动构造函数,且没有实现析构函数 、拷⻉构造、拷⻉赋值重载中的任意⼀个。那么编译器会⾃动⽣成⼀个默认移动构造。默认⽣成的移动构造函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调⽤移动构造,没有实现就调⽤拷⻉构造。
如果你没有⾃⼰实现移动赋值重载函数,且没有实现析构函数 、拷⻉构造、拷⻉赋值重载中的任意⼀个,那么编译器会⾃动⽣成⼀个默认移动赋值。默认⽣成的移动构造函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调⽤移动赋值,没有实现就调⽤拷⻉赋值。(默认移动赋值跟上⾯移动构造完全类似)
如果你提供了移动构造或者移动赋值,编译器不会⾃动提供拷⻉构造和拷⻉赋值。
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
,_age(age)
{}
private:
string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
Person s4;
s4 = std::move(s2);
return 0;
}
该类没有自己实现析构函数,拷贝构造以及拷贝赋值重载,所以编译器会生成默认的移动构造和移动赋值。
2.2成员变量声明时给缺省值
成员变量声明时给缺省值是给初始化列表⽤的,如果没有显⽰在初始化列表初始化,就会在初始化列表⽤这个值去初始化。
上述代码没有显示初始化列表,所以用声明时的缺省值进行初始化。
2.3default和delete
C++11可以让你更好的控制要使⽤的默认函数。假设你要使⽤某个默认的函数,但是因为⼀些原因这个函数没有默认⽣成。⽐如:我们提供了拷⻉构造,就不会⽣成移动构造了,那么我们可以使⽤default关键字显⽰指定移动构造⽣成。
如果想要限制某些默认函数的⽣成,在C++98中,将该函数设置成private,并且不实现,这样只要其他⼈想要调⽤就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指⽰编译器不⽣成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{}
Person(Person&& p) = default;
//Person(const Person& p) = delete;
private:
string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}
2.4final与override
C++中的继承、多继承、菱形继承以及继承和组合和C++多态、虚函数以及抽象类有对这两个关键字的介绍。
3.C++11后STL中的一些变化
1. 下图圈起来的就是C++11之后STL中新的容器,最有用的是unordered_map和unordered_set。
2.STL中容器的新接口也不少,最重要的就是右值引用和移动语义相关的push/insert/emplace系列,移动构造和移动赋值,还有initializer_list版本的构造等。
3.容器的范围for遍历。
4.lambda
4.1lamda表达式语法
lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。
lambda表达式的格式: [ capture-list ] ( parameters ) -> return type { function boby }。
[capture-list] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下⽂中的变量供 lambda 函数使用,捕捉列表可以传值和传引⽤捕捉,捕捉列表为空也不能省略。
(parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()⼀起省略。
->return type :返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进⾏推导。
{function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使⽤所有捕获到的变量,函数体为空也不能省略。
void swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
auto swap1 = [](int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
};
int main()
{
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(1, 2) << endl;
//1.捕捉列表为空也不能省略
//2.参数列表为空可以省略
//3.返回值可以省略,可以通过返回对象自动推导
//4.函数体不能省略
auto func1 = []
{
cout << "hello bit" << endl;
return 0;
};
func1();
int a = 0, b = 1;
swap1(a, b);
cout << "a = " << a << ",b = " << b << endl;
return 0;
}
总结:(1) 捕捉列表为空也不能省略,参数列表为空可以省略,返回值可以省略,可以通过返回对象自动推导,函数体不能省略。(2)lambda表达式可以定义在全局,使用和函数一样。
4.2捕捉列表
1.lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量,如果想⽤外层作⽤域中的变量就需要进⾏捕捉。
2.第⼀种捕捉⽅式是在捕捉列表中显⽰的传值捕捉和传引⽤捕捉,捕捉的多个变量⽤逗号分割。[x,y, &z] 表⽰x和y值捕捉,z引⽤捕捉。
3.第⼆种捕捉⽅式是在捕捉列表中隐式捕捉,我们在捕捉列表写⼀个=表⽰隐式值捕捉,在捕捉列表写⼀个&表⽰隐式引⽤捕捉,这样我们 lambda 表达式中⽤了那些变量,编译器就会⾃动捕捉那些变量。
4.第三种捕捉⽅式是在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=, &x]表⽰其他变量隐式值捕捉,x引⽤捕捉;[&, x, y]表⽰其他变量引⽤捕捉,x和y值捕捉。当使⽤混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。
5.lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量不需要捕捉, lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
int x = 0;
//捕捉列表必须为空,因为全局变量不用捕捉就可以用,没有可被捕捉的变量
auto func1 = []
{
x++;
return x;
};
int main()
{
//只能用当前lambda局部域和捕捉的对象和全局对象
int a = 0, b = 1, c = 2, d = 3;
auto func1 = [a, &b]
{
//值捕捉的变量不能修改,引用捕捉的变量可以修改
//a++;
++b;
int ret = a + b;
return ret;
};
//如果有同名函数,就近调用
func1();
cout << b << endl;
cout << func1() << endl;
//隐式值捕捉 -- 用哪些变量就捕捉哪些变量
auto func2 = [=]
{
int ret = a + b + c;
return ret;
};
cout << func2() << endl;
//隐式引用捕捉 -- 用哪些变量就捕捉哪些变量
auto func3 = [&]
{
a++;
b++;
d++;
};
func3();
cout << a << " " << b << " " << c << " " << d << endl;
//混合捕捉同理
return 0;
}
6.默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。
int main()
{
int a = 0, b = 1;
// 传值捕捉本质是⼀种拷⻉,并且被const修饰了
// mutable相当于去掉const属性,可以修改了
// 但是修改了不会影响外面被捕捉的值,因为是⼀种拷⻉
auto func1 = [=]()mutable //参数列表不能省略
{
a++;
b++;
cout << "lambda表达式里:";
cout << "a = " << a << " b = " << b << endl;
};
func1();
cout << "lambda表达式外:";
cout << "a = " << a << " b = " << b << endl;
return 0;
}
4.3lambda的应用
在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。
lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等, lambda 的应⽤还是很⼴泛的,下面我们就用lambda来代替仿函数写一个比较函数的例子。
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 } };
//这里实现仿函数去比较,一个项要实现两个
//如果像比较评价,还要再实现两个仿函数
//这里用lambda表达式就方便很多
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
sort(v.begin(), v.end(), [](const Goods& g1,const Goods& g2) {
return g1._price < g2._price;
});
sort(v.begin(), v.end(), [](const Goods& g1, Goods& g2) {
return g1._price > g2._price;
});
sort(v.begin(), v.end(), [](const Goods& g1, Goods& g2) {
return g1._evaluate < g2._evaluate;
});
sort(v.begin(), v.end(), [](const Goods& g1, Goods& g2) {
return g1._evaluate > g2._evaluate;
});
return 0;
}
4.4lambda的原理
lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个 lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。
上⾯的原理,我们可以透过汇编层了解⼀下。
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.049;
//lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
//函数对象
Rate r1(rate);
r1(10000, 2);
r2(10000, 2);
return 0;
}
lambda的本质就是仿函数,只是这个仿函数的类是系统自动生成的。
5.包装器
5.1function
std::function 是⼀个类模板,也是⼀个包装器。 std::function 的实例对象可以包装存储其他的可以调⽤对象,包括函数指针、仿函数、静态成员函数、普通成员函数、lambda 、bind 表达式等,存储的可调⽤对象被称为 std::function 的⽬标。若 std::function 不含⽬标,则称它为空。调⽤空std::function 的⽬标导致抛出 std::bad_function_call 异常。包装器的本质也是仿函数。
template <class T>
class function;
template <class Ret, class... Args>
class function<Ret(Args...)>; //这里偏特化的参数编译器进行了特殊处理
以上是 function 的原型,他被定义<functional>头⽂件中。https://zh.cppreference.com/w/cpp/utility/functional/function是function的官⽅文档链接。
函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型。
5.1.1包装器包装函数,仿函数对象,lambda表达式,静态成员函数
#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:
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; }; //包装lambda表达式
//包装静态成员函数
//静态成员函数要指定类域并且前面加&才能获取地址,也可以不加
function<int(int, int)> f4 = &Plus::plusi;
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
cout << f4(1, 1) << endl;
return 0;
}
5.1.2包装器包装普通成员函数
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.普通成员函数必须加&才能获取地址
//2.普通成员函数还有一个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以
//3.比较f5和f6,第一个this指针的地方f5是Plus*类型,f6是Plus类型,
//这里要清楚,this指针是不能显示的传递的,所以这里包装器包装之后
//第一个参数是Plus的指针,就用指针去调this指针
//第一个参数是Plus的对象,就用对象去调this指针
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.111, 1.1) << endl;
function<double(Plus, double, double)> f6 = &Plus::plusd;
cout << f6(pd, 1.111, 1.1) << endl;
cout << f6(Plus(), 1.111, 1.1) << endl; //用对象这里就可以使用匿名对象
//传匿名对象这里也可以是右值引用
function<double(Plus&&, double, double)> f7 = &Plus::plusd;
cout << f7(move(pd), 1.111, 1.1) << endl;
cout << f7(Plus(), 1.111, 1.1) << endl;
return 0;
}
5.2bind
bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象,本质还是一个仿函数。bind 可以⽤来调整参数个数和参数顺序。bind 也在<functional>这个头⽂件中。
调⽤bind的⼀般形式: auto newCallable_obj = bind(callable_obj,arg_list); 其中newCallable_obj本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable_obj的参数。当我们调⽤newCallable_obj时,newCallable_obj会调⽤callable_obj,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰newCallable_obj的参数,它们占据了传递给newCallable_obj的参数的位置。数值n表⽰⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3....这些占位符放到placeholders的⼀个命名空间中。
5.2.1bind用于调整参数顺序
这里关注sub2,bind(Sub, _2, _1)中,_1这个位置接收传给sub2的第一个实参,_2这个位置接收传给sub2的第二个实参,当前可以理解成sub2 = bind(Sub, 5, 10),调用sub2时调用Sub(5, 10),所以得到的结果是-5.
#include <functional>
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{
return a - b;
}
int main()
{
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl;
return 0;
}
5.2.2bind用于调整参数个数
这里实现的是将上述减法中的被减数或者减数固定死。下面将Sub的第一个参数绑定死,绑定成100,这样使用时只用传递一个参数了。也可以绑定死第二个参数。
int main()
{
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;
auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl;
return 0;
}
这里用于绑定上述例子中需要传递的匿名对象。
class Plus
{
public:
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
function<double(double, double)> plus = bind(&Plus::plusd, Plus(), _1, _2);
cout << plus(1.1, 2.2) << endl;
return 0;
}
6.C++11语法的一个综合使用的算法题
该题大致的解题思路是遇到运算数,将其放入栈中,遇到运算符,依次从栈中取出右操作数和左操作数进行运算,运算完之后将结果再次存入栈中,最后栈中只有一个元素,该元素就是最后的结果。
// 传统⽅式的实现
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中的新语法进行实现。使用map构建运算符和对应对应运算函数function的映射关系。
这里创建一个map对象opFuncMap,里面是运算符和对应运算操作的可调用对象。里面的可调用对象这里使用lambda表达式来写。int ret = opFuncMap[str](left, right);这行代码就是最关键的一行,这里是一个map对象opFuncMap调用operator[]的重载,返回对应运算符的可调用对象,后面的(left, right)是该可调用对象的参数,运行完对应运算之后返回一个结果值。
这段程序中使用的C++11中的新语法有:(1)包装器function。(2)lambda表达式。(3)多参数的隐式类型转换(pair对象)。
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
map<string, function<int(int, int)>> opFuncMap = {
{"+", [](int x, int y) {return x + y; }},
{"-", [](int x, int y) {return x - y; }},
{"*", [](int x, int y) {return x * y; }},
{"/", [](int x, int y) {return x / y; }}
};
for (auto& str : tokens)
{
if (opFuncMap.count(str)) //如果在map中能找到str,执行下面逻辑
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
int ret = opFuncMap[str](left, right);
st.push(ret);
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};