文章目录
- 一、可变参数模板
- 1.1 可变参数的函数模板
- 1.2 递归函数方式展开参数包
- 1.3 逗号表达式展开参数包
- 1.4 empalce相关接口函数
- 二、包装器function
- 2.1 function用法
- 2.2 例题:逆波兰表达式求值
- 2.3 验证
- 三、绑定函数bind
- 3.1 调整参数顺序
- 3.2 固定绑定参数
一、可变参数模板
在C语言中其实也有可变参数:
1.1 可变参数的函数模板
C++库里面也有很多使用可变参数函数模板的:
template <class ...Args>
void fun(Args... args)
{}
Args是一个模板参数包,args是一个函数形参参数包
声明一个参数包Args…args,这个参数包中可以包含0到任意个模板参数
以前只能传递一个对象做参数,有了可变参数包就可以传递0~n个参数:
template <class ...Args>
void fun(Args... args)
{
// 获取参数包中有几个参数
cout << sizeof...(args) << endl;
}
int main()
{
fun();
fun(1);
fun(1, 1.1);
fun(1, 1.1, std::string("abc"));
std::vector<int> v;
fun(1, 1.1, std::string("abc"), v);
return 0;
}
那么怎么把这些参数取出来呢?
1.2 递归函数方式展开参数包
void fun()
{
cout << endl;
}
template <class T, class ...Args>
void fun(T val, Args... args)
{
cout << val << " ";
fun(args...);
}
int main()
{
fun();
fun(1);
fun(1, 1.1);
fun(1, 1.1, std::string("abc"));
return 0;
}
解释:
按照箭头的方式调用,最后当没有参数的时候就会走最上面的函数
1.3 逗号表达式展开参数包
template <class T>
void printArg(T val)
{
cout << val << " ";
}
template <class ...Args>
void fun(Args... args)
{
int arr[] = { (printArg(args), 0)... };
cout << endl;
}
int main()
{
fun(1, 1.1, std::string("abc"));
return 0;
}
这种展开参数包的方式,不需要通过递归终止函数,printArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。
(printArg(args), 0)
:先执行printArg(args)
,再得到逗号表达式的结果0。通过初始化列表来初始化一个变长数组, {(printArg(args), 0)...}
将会展开成((printArg(arg1),0)
,(printArg(arg2),0)
, (printArg(arg3),0), etc... )
,最终会创建一个元素值都为0的数组。在创建数组的过程中会先执行逗号表达式前面的部分printArg(args)
打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
1.4 empalce相关接口函数
比较:
insert
:
emplace
:
emplace的使用:
int main()
{
std::list<int> lt;
lt.emplace_back();
lt.emplace_back(1);
lt.emplace_back(2);
for (auto& e : lt)
{
cout << e << " ";
}
cout << '\n';
return 0;
}
而emplace在插入自定义类型数据的时候会有区别:
struct A
{
A(int a = 1, double b = 2)
: _a(a)
, _b(b)
{}
int _a;
double _b;
};
int main()
{
std::list<A> lt;
lt.push_back({ 1, 1.0 });
lt.emplace_back(2, 2.0);
//lt.push_back(3, 3.0);// error
for (auto& e : lt)
{
cout << e._a << endl;
}
cout << '\n';
return 0;
}
上面使用push_back是先构造再拷贝构造,而使用emplace_back就可以直接构造(使用参数包)。
验证一下:
引入之前写过的string类
namespace yyh
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string(const char* str = "")
: _size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];// _capacity表示有效字符个数
strcpy(_str, str);
cout << "string(const char* str) -- 构造函数" << endl;
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
: _size(strlen(s._str))
, _capacity(s._size)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
cout << "string(const string& s) -- 深拷贝" << endl;
}
// 移动构造
string(string&& s)
{
cout << "string(string&& s) -- 移动拷贝" << endl;
swap(s);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
}
struct A
{
A(int a = 1, const char* str = " ")
: _a(a)
, _str(str)
{}
int _a;
yyh::string _str;
};
push_back
:
emplace_back
:
总结:
如果是左值,使用push_back
或者emplace_back
没什么区别。
对于右值,emplace_back
把构造和拷贝构造合二为一成构造函数。
但是如果没有移动拷贝效率差距会很大,emplace_back
还是直接构造,但是push_back
就会走深拷贝。
二、包装器function
其实包装器function就是一个类模板。先来看一段代码:
template <class F, class T>
void func(F fun, T val)
{
static int cnt = 1;
cout << "cnt: " << cnt++ << endl;
cout << "&cnt: " << &cnt << endl;
}
int f1(int x)
{
return x * 2;
}
struct f2
{
int operator()(int x)
{
return x * 2;
}
};
int main()
{
// 函数名
func(f1, 2);
// 仿函数对象
func(f2(), 2);
// lambda表达式
func([](int x)->int { return x * 2; }, 2);
return 0;
}
可以看到以三种不同的方式调用func函数,func函数就会被实例化出三份。
包装器可以很好的解决上面的问题
2.1 function用法
如果参数是两个int的话就是:
function<int(int, int)> fun1
int f1(int x)
{
return x * 2;
}
struct f2
{
int operator()(int x)
{
return x * 2;
}
};
class f3
{
public:
static int muli(int x)
{
return x * 2;
}
double muld(double x)
{
return x * 2;
}
};
int main()
{
// 普通函数
function<int(int)> fun1(f1);
cout << fun1(2) << endl;
// 仿函数
function<int(int)> fun2;
fun2 = f2();
cout << fun2(2) << endl;
// lambda表达式
function<int(int)> fun3;
fun3 = [](int x)->int {return 2 * x; };
cout << fun3(2) << endl;
// 静态成员函数指针
function<int(int)> fun4 = &f3::muli;
cout << fun4(2) << endl;
// 非静态成员函数指针
function<int(f3/*this指针*/, int)> fun5 = &f3::muld;
cout << fun5(f3(), 2) << endl;
return 0;
}
这里要注意类成员函数的调用方法:
对于静态成员函数,因为没有this指针,所以正常调用,后面也可以不加&
对于非静态成员函数,因为含有this指针,而this指针不能显示传递,所以要传递对象。必须加&。
当然也可以不在()内部加上对象,可以使用lambda表达式中的[]捕获:
f3 ff;
function<int(int)> fun6 = [&ff](int x)->double {return ff.muld(x); };
2.2 例题:逆波兰表达式求值
题目链接
具体做法就不多叙述,这里主要展示怎么使用function函数:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
map<string, function<int(int, int)>> hash =
{
{"+", [](int x, int y)->int{return x + y;}},
{"-", [](int x, int y)->int{return x - y;}},
{"*", [](int x, int y)->int{return x * y;}},
{"/", [](int x, int y)->int{return x / y;}},
};
for(auto& e : tokens)
{
if(hash.count(e) == 0)
{
st.push(stoi(e));
}
else
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
st.push(hash[e](left, right));
}
}
return st.top();
}
};
2.3 验证
在2.1中我们看道以那种方式会实例化三个模板函数。
而我们可以用function来解决这个问题:
template <class F, class T>
T func(F fun, T val)
{
static int cnt = 1;
cout << "cnt: " << cnt << endl;
cout << "&cnt: " << &cnt << endl;
return fun(val);
}
int f1(int x)
{
return x * 2;
}
struct f2
{
int operator()(int x)
{
return x * 2;
}
};
class f3
{
public:
static int muli(int x)
{
return x * 2;
}
double muld(double x)
{
return x * 2;
}
};
int main()
{
// 函数名
func(function<int(int)>(f1), 2);
// 仿函数对象
f2 ff;
func(function<int(int)>(ff), 2);
// lambda表达式
func(function<int(int)>([](int x)->int {return x * 2; }), 2);
return 0;
}
可以看出只实例化出了一份函数。
三、绑定函数bind
3.1 调整参数顺序
int Plus(int a, int b)
{
return a - b;
}
int main()
{
function<int(int, int)> fun1 = bind(Plus, placeholders::_1, placeholders::_2);
cout << fun1(1, 2) << endl;
function<int(int, int)> fun2 = bind(Plus, placeholders::_2, placeholders::_1);
cout << fun2(1, 2) << endl;
return 0;
}
从这里就可以看出_1代表第一个参数,_2代表第二个参,对于fun2就相当于把传参的顺序改变了。
3.2 固定绑定参数
class fun
{
public:
static int muli(int x)
{
return x * 2;
}
double muld(double x)
{
return x * 2;
}
};
int main()
{
// 非静态成员函数指针
function<int(fun/*this指针*/, int)> fun1 = &fun::muld;
cout << fun1(fun(), 2) << endl;
return 0;
}
上面说过了使用非静态成员函数的时候得传递对象进去。如果我们不想传递这个参数呢?
class fun
{
public:
static int muli(int x)
{
return x * 2;
}
double muld(double x)
{
return x * 2;
}
};
int main()
{
// 非静态成员函数指针
function<int(fun/*this指针*/, int)> fun1 = &fun::muld;
cout << fun1(fun(), 2) << endl;
// 绑定参数
function<int(int)> fun2 = bind(&fun::muld, fun(), std::placeholders::_1);
cout << fun2(2) << endl;
return 0;
}