参数包
下面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数。我们虽然可以通过sizeof...(args)得到形参的个数,但不能遍历形参,语法不支持args[i]获取形参。
下面有两种方法获取每个形参
递归函数方式展开参数包
传过来的形参分为两部分T Args ,每次取一个T形参,剩余的形参再继续递归取。当只剩一个形参时,更符合递归终止函数,结束递归。
逗号表达式展开参数包
1.利用逗号表达式先执行左边,再以右边为结果。
2.初始化列表,通过初始化列表来初始化一个变长数组。
emplace_back
首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。emplace_back和push_back有什么不同呢?
对链表插入一个string对象,push_back必须先初始化一个string对象,再插入。如果插入对象是左值还需要进行拷贝构造插入。即使是插入右值,还得构造+移动构造。
而emplace只需要一次构造就可以。
emplace原理
emplace函数模板根据参数包的不同,实例化出不同的emplace函数,再找到与之相符的构造函数。
template <class... Args>
ListNode(Args&&... args): _next(nullptr), _prev(nullptr), _data(std::forward<Args>(args)...)
{}template <class... Args>
void emplace_back(Args&&... args)
{insert(end(), std::forward<Args>(args)...);
}// 原理:编译器根据可变参数模板生成对应参数的函数
/*void emplace_back(string& s)
{insert(end(), std::forward<string>(s));
}void emplace_back(string&& s)
{insert(end(), std::forward<string>(s));
}void emplace_back(const char* s)
{insert(end(), std::forward<const char*>(s));
}void emplace_back(size_t n, char ch)
{insert(end(), std::forward<size_t>(n), std::forward<char>(ch));
}*/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 newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);
}
insert(end(), std::forward<Args>(args)...); 完美转发保持右值属性。
lambda
仿函数
当我们对自定义类型进行排序时,我们需要实现一个类,在类中重载operator()(),生成仿函数。通过传仿函数,取得自定义类型的比较方式。
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语法中出现了Lambda表达式。
lambda表达式语法
lambda表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement }
[capture-list] : 捕捉列表,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
捕捉的是 实现仿函数类的初始化参数
(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
连同()一起省略。
mutable:默认情况下,lambda函数总是一个const函数,加上mutable可以取消其常量
性。使用该修饰符时,()不可省略(即使参数为空)。->returntype:返回值类型。可以省略返回值类型,不管有无返回值。
{statement}:函数体。在该函数体内,除了可以使用()的参数,还可以使用[ ]捕获的参数,还有全局变量。
除了[] {} 不可以省略,不需要的话其余都可以省略。
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使用,以及使用的方式传值还是传引用。
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)(传值有const修饰,加上mutable可以修改lambda可以用的变量,但出了lambda作用域就会变回原来的值。)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)(可以对变量进行修改,出了lambda 作用域也保持修改后的值。)
虽然我们说[=]/[&]捕获所有父作用域中的变量,但只有用到的变量才会捕获。
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.14;Rate ret(rate);auto func = [rate](double money,double year)->double{return money * rate * year;};cout << ret(100, 5) << endl;cout << func(100, 5) << endl;}
[ ]里面参数相当于仿函数类初始化的参数,传给构造函数。()相当于仿函数的参数。
可以看到底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载operator()。
lambda函数命名方式是lambda_uuid 后面加上唯一识别码,虽然两个lambda函数实现完全相同,但它们类型是
不相同的,地址也不同 不能进行赋值。