一、可变参数模板
C++11的新特性可变参数模板
能够让您创建可以接受
可变参数的函数模板和类模板
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
参数包是不支持下标解析的
1.1 递归函数方式展开参数包
// 递归终止函数
void ShowList()
{
cout << endl;
}
// 展开函数
// 增加一个模板参数让编译器自己去解析这个参数包里的东西
template <class T, class ...Args>
void ShowList(const T& value, Args... args)
{
cout << value <<" ";
ShowList(args...); // 如果是0个参数的参数包就会调用void ShowList()
// 如果参数包接收的char类型的参数,会再去调匹配的ShowList函数
// 调不到就只能调自己,根据模板推导类型
// 打印完,参数包再往下传,0个参数就调用void ShowList()
// void ShowList()可以认为是递归终止函数
}
int main()
{
ShowList(); // 编译器会找最匹配的,调void ShowList()
ShowList(1); // 1传给value,后面的参数包就没有了,参数包代表0-n个参数
ShowList(1, 'A'); // 1传给value,'A'传给参数包
ShowList(1, 'A', std::string("sort"));
return 0;
}
二、lambda表达式
C++98中
对一个数据集合中的元素排序
可以使用std::sort
如果待排序元素为自定义类型
需要自己写仿函数
如果每次按自定义类型不同的成员变量
进行排序就要写多个仿函数,十分不方便
因此,C++11语法中的Lambda表达式
便是解决此类问题
lambda表达式
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表达式是一个匿名函数
1.1 lambda表达式语法
书写格式:
[捕捉列表] (参数列表) mutable ->return-type { 函数体 }
- 捕捉列表:该列表总出现lambda函数开始位置
编译器根据[]判断接下来的代码是否为lambda函数
捕捉列表能够捕捉上下文中的变量供lambda函数使用 - 参数列表:与普通函数的参数列表一致
如果不需要参数传递,则可以连同()一起省略 - mutable:默认情况lambda函数总是const函数,mutable可以取消其常量性
使用该修饰符参数列表不可省略(即使参数为空) - ->return-type:返回值类型
用追踪返回类型形式声明函数的返回值类型
无返回值可略,有返回值也可略,由编译器推导 - { 函数体 }:该函数体内除了可使用其参数外
还可以使用所有捕获到的变量
用lambda表达式实现add
auto add = [](int x, int y)->int { return x + y; };
// cout << [](int x, int y)->int { return x + y; }(1, 2) << endl; // 这样写比较抽象
cout << add(1, 2) << endl;
auto add2 = [](int x, int y)
{
return x + y;
};
cout << add2(3, 2) << endl;
[] {}; // 最简单的lambda,捕捉列表和函数体是一定不能省略的
用lambda对自定义类型比较大小
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 } };
// 用lambda对自定义类型比较大小
// <
auto priceLess = [](const Goods& g1, const Goods& g2)->bool { return g1._price < g2._price; };
sort(v.begin(), v.end(), priceLess);
// >
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {
return g1._price > g2._price; });
// 比较评价
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {
return g1._evaluate > g2._evaluate; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {
return g1._evaluate < g2._evaluate; });
return 0;
}
捕捉变量
int main()
{
int x = 1, y = 2;
auto swap1 = [](int& rx, int& ry)
{
// 只能用当前作用域的变量
int tmp = rx;
rx = ry;
ry = tmp;
};
swap1(x, y);
cout << x << " " << y << endl;
// 还可以换一种写法
// 想用外面的变量,则可以利用捕捉列表进行捕捉(捕捉过来的对象是外面对象的拷贝)
/*
// 传值捕捉
auto swap2 = [x, y]() mutable // 捕捉多个值用逗号分割即可;直接给值叫做传值捕捉,传值捕捉无法修改,加上mutable(异变)就可以修改
{
int tmp = x;
x = y;
y = tmp;
};
swap2();
cout << x << " " << y << endl;
*/
// mutable用得比较少,建议
// 引用捕捉
auto swap2 = [&x, &y]()
{
int tmp = x;
x = y;
y = tmp;
};
swap2();
cout << x << " " << y << endl;
// 还可以混合捕捉,x引用捕捉,y传值捕捉
// 全部引用捕捉
auto func1 = [&]()
{
// ...
};
// 全部传值捕捉
auto func2 = [=]()
{
// ...
};
// 全部引用捕捉,x传值捕捉
auto func3 = [&, x]()
{
// ...
};
return 0;
}
1.2 函数对象与lambda表达式
函数对象,又称为仿函数
即可像函数一样使用的对象
(在类中重载了operator()运算符的类对象)
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;
Rate r1(rate);
r1(10000, 2);
// lamber
auto r2 = [=](double monty, int year)->double{return monty*rate*year;
};
r2(10000, 2);
return 0;
}
lambda表达式大小为1个字节
在编译器角度是没有lambda
定义一个lambda
编译器自动生成一个仿函数对象的类型
在该类中重载了operator()
该类是一个空类,空类没给成员变量就是一个字节
函数对象将rate作为其成员变量
在定义对象时给出初始值即可
lambda表达式通过捕获列表可以
直接将该变量捕获到
三、包装器
C++中的function本质是一个类模板
也是一个包装器
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
int main()
{
// int(*pf1)(int, int) = f; // 不是常规的指针类型,写法复杂
// 假设要求要声明一个统一的类型
// map<string, > // 这里要声明可调用类型,f和Functor调用起来都是一样的,但类型不一样,一个是函数指针一个是类
// 这时候就没法声明,而包装器就可以统一封装出可调用类型
function<int(int, int)> f1 = f; // 返回值加参数包,参数包就是把实际要的类型写上
function<int(int, int)> f2 = Functor(); // Function可以对函数指针和仿函数对象进行包装
function<int(int, int)> f3 = [](int a, int b) { return a + b; };
cout << f1(1, 2) << endl; // 包装以后两个类型的对象是一样的
cout << f2(2, 2) << endl;
cout << f3(3, 3) << endl;
map<string, function<int(int, int)>> opFuncMap;
opFuncMap["函数指针"] = f;
opFuncMap["仿函数"] = Functor();
opFuncMap["lambda"] = [](int a, int b) { return a + b; };
// 包装器的作用:更好的控制可调用对象的类型
return 0;
}
静态成员和非静态成员的包装
class Plus
{
public:
Plus(int rate)
: _rate(rate)
{}
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _rate;
}
private:
int _rate = 2;
};
int main()
{
function<int(int, int)> f1 = Plus::plusi; // 包装静态成员函数正常包装就可以
// 非静态成员函数,就不能这样直接包装
// function<double(double, double)> f2 = Plus::plusd;
// error C3867: “Plus::plusd”: 非标准语法;请使用 "&" 来创建指向成员的指针
// 普通成员函数名代表函数指针.静态成员也一样,指定类域就可取到这个函数指针
// 非静态成员需要加一个&
// 非静态传参还有一个this指针需要传参
// error C2440 : “初始化”: 无法从“double(__cdecl Plus::*)(double, double)”转换为“std::function<double(double, double)>”
function<double(Plus, double, double)> f2 = &Plus::plusd;
// 也可以传Plus*,左值能被取地址,右值不行,匿名对象是右值不能取地址,就不能用匿名对象
// 传指针也可以,传对象也可以,因为这不是直接去掉用plusd这个函数,我是一个包装器,f1是直接调用Plusi
// f2是用对象去掉用Plusd
// 当Plusd是指针,就用指针取调用Plusd
// 如果是对象就用对象调用Plusd
cout << f1(1, 2) << endl;
cout << f2(Plus(2), 20, 20) << endl; // 第一个正常调用,第二个需要加一个匿名对象;需要写一个构造函数,也可以不写,Plus就不能传参
// 也可以不用匿名对象
Plus p1(3);
cout << f2(p1, 20, 20) << endl;
return 0;
}
bind
调整参数顺序
void Print(int a, int b)
{
cout << a << " ";
cout << b << endl;
}
int main()
{
Print(10, 20);
// auto RPrint = bind(Print, placeholders::_2, placeholders::_1); // 第一个参数传可调用对象,_1是一个占位符也是第一个参数,-2是第二个参数以此类推,默认是拿不到的,它放在placeholders命名空间里面
function<void(int, int)> RPrint = bind(Print, placeholders::_2, placeholders::_1);
// 两种写法都可以推荐用auto
RPrint(10, 20);
return 0;
}
调整参数个数
class Sub
{
public:
Sub(int rate)
: _rate(rate)
{}
int func(int a, int b)
{
return (a - b) * _rate;
}
private:
int _rate;
};
int main()
{
function<int(Sub, int, int)> fSub = &Sub::func;
cout << fSub(Sub(3), 10, 20) << endl;
function<int(int, int)> fSub1 = bind(&Sub::func, Sub(3), placeholders::_1, placeholders::_2);
cout << fSub1(10, 20) << endl;
// 把隐藏this指针绑死就只用传两个参数
// 把第二个参数绑死
function<int(Sub, int)> fSub2 = bind(&Sub::func, placeholders::_1, 100, placeholders::_2);
// 第二个参数绑死了,第三个参数是_2,还是按顺序挨着走
cout << fSub2(Sub(3), 20) << endl;
}
本篇博客完,感谢阅读🌹
如有错误之处可评论指出
博主会耐心听取每条意见