1.可变模板参数
C++11 引入的可变模板参数(variadic templates)使得模板参数的数量可以是任意多个,极大地提升了 C++ 的模板编程能力。以下是 C++11 中可变模板参数的详细总结:
1. 基本语法
在模板参数列表中,通过 ...
(三个点)来表示可变参数。常见的使用形式如下:
template <typename... Args>
void func(Args... args);
在这里,Args...
表示一个可变数量的模板参数,args...
是与之对应的函数参数。
2. 可变模板参数的展开
可变模板参数的展开是指将模板参数包展开成实际的参数。展开的方式有两种:
- 直接展开
- 递归展开
直接展开
直接展开通常用于函数调用或表达式中。可以通过递归展开实现对每个参数的单独处理。
例如,可以直接在参数包上应用表达式:
template <typename... Args>
void print(Args... args) {
(std::cout << ... << args) << '\n'; // C++17 的折叠表达式
}
可使用sizeof求出传了多少个参数
上面的例子展示了一个 C++17 的特性:折叠表达式。C++11 中,可以用逗号表达式和递归结合实现类似效果。
递归展开
在 C++11 中实现递归展开通常是通过递归调用函数模板实现。例如:
void print() { }
template <typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // 递归展开
}
这段代码中,第一个参数 first
被单独处理,然后剩下的参数 rest...
递归调用 print
,直到参数包为空。
使用这三个函数可以输出各个参数
3. 参数包展开的用途
可变模板参数的引入,让函数、类和类型定义都可以处理任意数量的参数,从而大大增强了 C++ 的泛型编程能力。
(1)函数模板中的应用
在可变参数模板中,可以定义接收任意数量参数的函数。例如,实现一个通用的 print
函数:
template <typename... Args>
void print(Args... args) {
int unpack[] = { (std::cout << args << " ", 0)... }; // 利用逗号表达式展开
std::cout << std::endl;
}
通过展开参数包,print
可以接受任意数量的参数。
(2)类模板中的应用
可变参数模板还可以用于定义类模板,例如实现一个元组(Tuple
)类型:
template <typename... Types>
class Tuple {
// 可以定义包含任意类型的元组
};
在此基础上,可以定义不同类型和数量的模板参数,以实现功能更为复杂的泛型数据结构。
(3)转发参数包
C++11 中引入了 std::forward
,可以和可变模板参数结合使用,实现完美转发:
template <typename... Args>
void wrapper(Args&&... args) {
func(std::forward<Args>(args)...); // 完美转发
}
这种用法让我们可以编写包装函数并将参数原样传递给其他函数。
4. 常见的使用模式
可变模板参数在泛型编程中具有以下常见使用模式:
(1)递归终止条件
在递归处理可变模板参数时,通常需要定义一个基函数(或基模板)作为递归终止条件:
void process() { } // 递归终止条件
template <typename T, typename... Args>
void process(T first, Args... rest) {
std::cout << first << " ";
process(rest...);
}
(2)逗号表达式展开
逗号表达式是一种展开参数包的技巧,通过将表达式放入数组初始化列表中展开参数包:
template <typename... Args>
void print_all(Args... args) {
int unpack[] = { (std::cout << args << " ", 0)... };
std::cout << std::endl;
}
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行
printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个 ***元素值都为0*** 的数组int arr[sizeof...(Args)]
。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数
5. push_back和 emplace_back 的比较
6. 可变模板参数的限制
虽然可变模板参数提供了灵活性,但它也有一些限制:
- 参数包的展开顺序是从左至右的,这在某些情况下需要特别注意。
- 对于参数包的展开,C++11 中没有提供直接的折叠表达式(这是 C++17 才引入的特性),因此在 C++11 中实现复杂的参数展开可能需要使用更多的递归和逗号表达式。
2.lambda 表达式
1.表示形式
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type {
statement }
- [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够
捕捉上下文中的变量
供lambda函数使用。 - (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
- mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表
不可省略
(即使参数为空)。 - ->returntype:
返回值类型
。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。 - {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
2.C++98中的一个例子
#include <algorithm>
#include <functional>
int main()
{
int array[] = {4,1,8,5,3,7,0,9,2,6};
// 默认按照小于比较,排出来结果是升序
std::sort(array, array+sizeof(array)/sizeof(array[0]));
// 如果需要降序,需要改变元素的比较规则
std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
return 0;
}
如果待排序元素为 自定义类型
,需要用户定义排序时的比较规则:
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());
}
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表达式实际是一个 匿名函数
。
auto fun1 = [](int c){b = a + c; };//最简单的函数调用,注意:fun1是一个函数
fun1(10);
3.捕获列表说明
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
- [var]:表示值传递方式捕捉变量var
- [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]:表示值传递方式捕捉当前的this指针
注意:
- 父作用域指包含lambda函数的语句块
- 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:
[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
3.捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
4.在块作用域以外的lambda函数捕捉列表必须为空
5.lambda表达式之间 不能相互赋值
1.举例说明
1.这是用lambda表达式写的交换函数,可以进行一定的改变
2.[var]:表示值传递方式捕捉变量var
(但是这样是无法调用的,因为x,y用【】捕捉后无法改变,默认const修饰
)需要加mutable
修饰才能改变
3.特别注意:捕捉到的x和y和原本的x和y不是同一个,因为是 传值传递
这次就是 引用传递
了,【】只能捕捉对象,不能捕捉对象的地址,要得到地址还得在()中传参
3.function包装器
要包头文件 《functional》
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
那么我们来看看,我们为什么需要function呢?
function可以将三种函数对象包装起来(分别是 函数指针
,仿函数
,和 lambda表达式
)
这么做可以利用map中重载的[]的功能,找到对应的函数对象,并进行调用
举例说明:
逆波兰表达式求值
原先解法:
class Solution {
public:
int operation(int a, int b, string s) {
if (s[0] == '+')
return b + a;
if (s[0] == '-')
return b - a;
if (s[0] == '*')
return b * a;
if (s[0] == '/')
return b / a;
return 1; // 记得写一个return
// 1,因为系统判定如果if都不走,那么就没有返回值
}
int evalRPN(vector<string>& tokens) {
int num = 0;
int result = 0;
string j;
int a;
int b;
int end;
int k;
for (int i = 0; i < tokens.size(); i++) {
if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" ||
tokens[i] == "/") {
brr.push(tokens[i]);
a = arr.top();
arr.pop();
b = arr.top();
arr.pop();
end = operation(a, b, brr.top());
arr.push(end);
brr.pop();
} else {
j = tokens[i];
if (j[0] == '-') {
for (int i = 1; i < j.size(); i++) {
num = num * 10 + (j[i] - '0');
}
arr.push(-num);
num = 0;
} else {
for (int i = 0; i < j.size(); i++) {
num = num * 10 + (j[i] - '0');
}
arr.push(num);
num = 0;
}
}
}
end = arr.top();
return end;
}
private:
stack<int> arr;
stack<string> brr;
};
functional做法:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
map<string, function<int(int, int)>> opFuncMap =
{
{ "+", [](int i, int j){return i + j; } },
{ "-", [](int i, int j){return i - j; } },
{ "*", [](int i, int j){return i * j; } },
{ "/", [](int i, int j){return i / j; } }
};
for(auto& str : tokens)
{
if(opFuncMap.find(str) != opFuncMap.end())
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
st.push(opFuncMap[str](left, right));//调用函数对象
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
标签:11,15,函数,int,移情别恋,参数,表达式,return,模板
From: https://blog.csdn.net/2301_80374809/article/details/143760724