【C++】C++11新特性——可变参数模板|function|bind

一、可变参数模板

在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;
}

在这里插入图片描述



猜你喜欢

转载自blog.csdn.net/qq_66314292/article/details/129636971