C++11特性(二)

news2024/9/21 4:34:54

在这里插入图片描述

系列文章目录

C++11特性(一)


文章目录

  • 系列文章目录
  • 前言
  • 一、可变模板参数
    • 1.1 什么是可变参数模板
    • 1.2 如何打印可变模板参数的内容
      • 递归函数方式展开参数包
      • 逗号表达式展开参数包
    • 1.3 emplace_back的实现
    • 1.4 可变模板参数为何高效
  • 二、lambda表达式
    • 2.1 C++98中的一个例子
    • 2.2 lambda表达式
    • 2.3 lambda表达式语法
      • 捕捉列表说明
    • 2.4 底层原理(函数对象与lambda表达式)
  • 三、包装器
    • 3.1 function包装器
    • 3.2 bind


前言

介绍了C++11前半部分的特性之后,紧接着介绍后面的内容。


一、可变模板参数

1.1 什么是可变参数模板

第一次接触可变参数是在c语言使用printf时的可变参数。
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。
我们学习基本的使用方法。
下面就是一个基本可变参数的函数模板:

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。

在没有可变模板参数时,我们通常像下面一样写类型不同,类型的个数固定的模板:

template<class T>
void ShowList(T&& x)
{
	//...
}

而可变模板参数的类型个数可变,类型也可变

int main()
{
	ShowList();
	ShowList(1);
	ShowList(1,"111");
	ShowList(1,"111",2.2);
	
	return 0;
}

可以通过sizeof来查看可变模板参数的参数个数:

template<typename ...Args>
void ShowList(Args...args)
{
	cout << sizeof...(args) << endl;	//注意这里的...在括号外,比较特殊
}
int main()
{
	ShowList();
	ShowList(1);
	ShowList(1, "111");
	ShowList(1, "111", 2.2);
	return 0;
}

运行结果:
在这里插入图片描述

1.2 如何打印可变模板参数的内容

像下面这样进行打印是不行的:

template<typename ...Args>
void ShowList(Args...args)
{
	cout << sizeof...(args) << endl;
	for (size_t i = 0; i < sizeof...(args); i++)
	{
		cout << args[i] << " ";
	}
	cout << endl;
}

c语言的可变参数是类似数组的方式存储的,可以理解为在运行时解析多个参数

c++这一部分是模板,模板是在编译时进行推导。所以这里是在编译阶段进行解析多个参数所以上面的代码是运行时逻辑,逻辑是走不通的。

那如何进行解析呢?

递归函数方式展开参数包

//递归终止函数
void Print()
{
	cout << endl;
}
template<class T, class ...Args>
void Print(T x, Args... args)
{
	cout << x << " ";
	Print(args...);	//参数包往下传时... 在后面
}
template<class ...Args>
void ShowList(Args...args)
{
	Print(args...);
}
int main()
{
	ShowList();
	ShowList(1);
	ShowList(1, "111");
	ShowList(1, "111", 2.2);
	return 0;
}

运行结果:
在这里插入图片描述
编译时逻辑就像下图一样,拿三个参数举例:
在这里插入图片描述
模板实例化出了多个不同的参数个数的函数:
在这里插入图片描述
这样我们就可以打印n个参数的内容。就像是模板的模板一样。支持多个参数。

逗号表达式展开参数包

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式

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)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包.

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();	//注意没有参数为0的函数
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

在这里插入图片描述

数组arr中实际的值都是0.可以使用返回值的形式进行构造数组arr:

template <class T>
int PrintArg(T t)
{
	cout << t << " ";
	return 0;
}
template<class ...Args>
void ShowList(Args...args)
{
	int arr[] = { PrintArg(args)... };
	cout << endl;
}

注意模板ShowList实例化出的就是:
在这里插入图片描述
单纯打印就可以这样:

template<class ...Args>
void ShowList(Args...args)
{
	int arr[] = { (cout << args << " ",0)...};
	cout << endl;
}

逻辑上通过。

STL容器中的empalce相关接口函数
http://www.cplusplus.com/reference/vector/vector/emplace_back/
http://www.cplusplus.com/reference/list/list/emplace_back/

template <class... Args>
void emplace_back (Args&&... args);

首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insertemplace系列接口的优势到底在哪里呢?

int main()
{
	std::list< std::pair<int, char> > mylist;
	// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
	// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
	mylist.emplace_back(10, 'a');
	mylist.emplace_back(20, 'b'); 	
	mylist.emplace_back(make_pair(30, 'c'));
	mylist.push_back(make_pair(40, 'd'));
	mylist.push_back({ 50, 'e' });
	for (auto e : mylist)
	cout << e.first << ":" << e.second << endl;
	return 0;
}

下面是我们自己实现的string接口:

#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
#include <assert.h>
namespace bit {
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{return _str;}
		iterator end()
		{return _str + _size;}
		const_iterator begin() const
		{return _str;}
		const_iterator end() const
		{return _str + _size;}
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			cout << "string(char* str)" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			reserve(s._capacity);
			for (auto ch : s)
			{
				push_back(ch);
			}
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			if (this != &s)
			{
				_str[0] = '\0';
				_size = 0;

				reserve(s._capacity);
				for (auto ch : s)
				{
					push_back(ch);
				}
			}
			return *this;
		}
		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动语义" << endl;
			swap(s);
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动语义" << endl;
			swap(s);
			return *this;
		}
		//n个val构造
		string(size_t n, char ch = '\0')
			:_str(nullptr)
			,_capacity(0)
			,_size(0)
		{
			cout << "string(size_t n, char ch = '\\0')" << endl;
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				_str[i] = ch;
			}
			_size = n;
			_str[_size] = '\0';
		}
		~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];
				if (_str)
				{
					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)
		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; // 不包含最后做标识的\0
	};
}
#include <list>
int main()
{
	// 下面我们试一下带有拷贝构造和移动构造的bit::string,再试试呢
	// 我们会发现其实差别也不到,emplace_back是直接构造了,push_back
	// 是先构造,再移动构造,其实也还好。
	std::list< std::pair<int, bit::string> > mylist;
	mylist.emplace_back(10, "sort");
	mylist.emplace_back(make_pair(20, "sort"));
	mylist.push_back(make_pair(30, "sort"));
	mylist.push_back({ 40, "sort"});
	return 0;
}

在这里插入图片描述

int main()
{
	list<bit::string> lt;

	bit::string s1("1111111");
	lt.emplace_back(s1);
	lt.emplace_back(move(s1));
	// 直接构造string参数包往下传,直接用string参数包构造string	
	lt.emplace_back("1111111");
	lt.emplace_back(10, 'x');
	cout << endl << endl;

	list <pair<bit::string, int>> lt1;
	// 构造pair + 拷贝/移动构造pair到list节点中的data上
	pair<bit::string, int> kv("苹果", 1);
	lt1.emplace_back(kv);
	lt1.emplace_back(move(kv));
	cout << endl << endl;

	// 直接构造pair的参数包往下传,直接用pair参数包构造pair
	lt1.emplace_back("苹果", 1);
	return 0;
}

在这里插入图片描述

1.3 emplace_back的实现

template<class T>
struct ListNode
{
	ListNode<T>* _next;
	ListNode<T>* _prev;
	T _data;
	ListNode(const T& data = T())
		:_next(nullptr)
		, _prev(nullptr)
		, _data(data)
	{}
	ListNode(T&& data)
		:_next(nullptr)
		, _prev(nullptr)
		, _data(move(data))
	{}
	template <class... Args>	//可变模板参数
	ListNode(Args&&... args)
		: _next(nullptr)
		, _prev(nullptr)
		, _data(std::forward<Args>(args)...)	//右值会退化
	{}
};

template<class T, class Ref, class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref, Ptr> Self;
	Node* _node;

	ListIterator(Node* node)
		:_node(node)
	{}
	// ++it;
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	Self operator++(int)
	{
		Self tmp(*this);
		_node = _node->_next;

		return tmp;
	}
	Self& operator--(int)
	{
		Self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}
};
template<class T>
class list
{
	typedef ListNode<T> Node;
public:
	typedef ListIterator<T, T&, T*> iterator;
	typedef ListIterator<T, const T&, const T*> const_iterator;
	iterator begin()
	{return iterator(_head->_next);}
	const_iterator begin() const
	{return const_iterator(_head->_next);}
	iterator end()
	{return iterator(_head);}
	const_iterator end() const
	{return const_iterator(_head);}

	void empty_init()
	{
		_head = new Node();
		_head->_next = _head;
		_head->_prev = _head;
	}
	list()
	{empty_init();}
	list(initializer_list<T> il)
	{
		empty_init();
		for (const auto& e : il)
			push_back(e);
	}
	// lt2(lt1)
	list(const list<T>& lt)
	{
		empty_init();
		for (const auto& e : lt)
			push_back(e);
	}
	// lt1 = lt3
	list<T>& operator=(list<T> lt)
	{
		swap(_head, lt._head);
		return *this;
	}
	~list()
	{
		clear();
		delete _head;
		_head = nullptr;
	}
	void clear()
	{
		auto it = begin();
		while (it != end())
			it = erase(it);
	}
	void push_back(const T& x)
	{
		insert(end(), x);
	}
	void push_back(T&& x)
	{
		insert(end(), move(x));
	}
	void pop_back()
	{
		erase(--end());
	}
	void push_front(const T& x)
	{
		insert(begin(), x);
	}
	void pop_front()
	{
		erase(begin());
	}

	// 没有iterator失效
	iterator insert(iterator pos, const T& x)
	{
		Node* cur = pos._node;
		Node* newnode = new Node(x);
		Node* prev = cur->_prev;
		// prev  newnode  cur
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = cur;
		cur->_prev = newnode;

		return iterator(newnode);
	}

	iterator insert(iterator pos, T&& x)
	{
		Node* cur = pos._node;
		Node* newnode = new Node(move(x));
		Node* prev = cur->_prev;

		// prev  newnode  cur
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = cur;
		cur->_prev = newnode;

		return iterator(newnode);
	}

	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  cur
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = cur;
		cur->_prev = newnode;

		return iterator(newnode);
	}

	// erase 后 pos失效了,pos指向节点被释放了
	iterator erase(iterator pos)
	{
		assert(pos != end());

		Node* cur = pos._node;
		Node* prev = cur->_prev;
		Node* next = cur->_next;

		prev->_next = next;
		next->_prev = prev;

		delete cur;
		return iterator(next);
	}
private:
	Node* _head;
};

测试代码:

int main()
{
	bit::list<bit::string> lt;

	bit::string s1("1111111");
	lt.emplace_back(s1);
	lt.emplace_back(move(s1));
	// 直接构造string参数包往下传,直接用string参数包构造string	
	lt.emplace_back("1111111");
	lt.emplace_back(10, 'x');
	cout << endl << endl;

	bit::list <pair<bit::string, int>> lt1;
	// 构造pair + 拷贝/移动构造pair到list节点中的data上
	pair<bit::string, int> kv("苹果", 1);
	lt1.emplace_back(kv);
	lt1.emplace_back(move(kv));
	cout << endl << endl;

	// 直接构造pair的参数包往下传,直接用pair参数包构造pair
	lt1.emplace_back("苹果", 1);
	return 0;
}

结果和库中的list一样:
在这里插入图片描述

1.4 可变模板参数为何高效

减少代码重复
通过使用可变模板参数,可以将具有相似逻辑但参数数量不同的函数或类模板进行统一处理,避免了为每个参数数量的组合单独编写重复的代码。例如,如果需要对不同数量的整数进行求和操作,使用可变模板参数可以只编写一个函数模板来处理,而不需要为每个可能的参数数量分别编写函数。

template<typename... Args>
int sum(Args... args) {
    int total = 0;
    (total += args,...);
    return total;
}

类型推导
编译器能够自动推导可变模板参数的类型,这使得代码更加简洁和灵活。在使用时不需要显式指定每个参数的类型,减少了类型声明的繁琐。
性能优化
在一些情况下,可变模板参数可以在编译时进行更多的优化。例如,编译器可以更好地展开循环、内联函数等,从而提高程序的运行效率。
灵活的接口设计
它为函数和类提供了更加灵活的接口,允许用户以不同的方式传递参数,增强了代码的通用性和可扩展性。
总的来说,可变模板参数在提高代码的复用性、简洁性和性能方面发挥了重要作用,使得 C++ 编程更加高效和强大。

二、lambda表达式

2.1 C++98中的一个例子

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。

#include <algorithm>
#include <functional>
int main()
{
	int array[] = { 4,1,8,5,3,7,0,9,2,6 };
	// 默认按照小于比较,排出来结果是升序 less<int>()
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	// 如果需要降序,需要改变元素的比较规则
	std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
	return 0;
}

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

#include <algorithm>
#include <functional>
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());
	return 0;
}

随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

2.2 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; });
}

2.3 lambda表达式语法

lambda表达式格式:[capture-list] (parameters) mutable -> return-type { statement}

  1. lambda表达式各部分说明:
  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导,最好是写上返回值。
  • {statement}函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:

  • lambda函数定义中,参数列表和返回值类型都是可选部分而捕捉列表和函数体可以为
    。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
    捕捉列表和函数体不能省略不写
int main()
{
	// 最简单的lambda表达式, 该lambda表达式没有任何意义
	[] {};
	// 省略参数列表和返回值类型,返回值类型由编译器推导为int
	[] {return  3; };
	// 省略了返回值类型,无返回值类型
	auto fun1 = [](int c) {};
	fun1(10);
	// 各部分都很完善的lambda函数
	auto fun2 = [](int c)->int {return c + 2; };
	fun2(10);
	return 0;
}

通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量

捕捉列表说明

写一个交换函数:

int main()
{
	int a = 1;
	int b = 2;
	cout << a << ":" << b << endl;
	//交换任意整形
	auto swap1 = [](int& x, int& y) {
		int tmp = x;
		x = y;
		y = tmp;
		};
	cout << a << ":" << b << endl;
	return 0;
}

运行结果发现结果没有变是怎么回事:在这里插入图片描述

	int a = 1;
	int b = 2;
	//交换a,b
	auto swap2 = []() {
		int tmp = a;
		a = b;
		b = tmp;
		};

发现表达式里不能使用a,b。得使用[]捕捉a,b

	//交换a,b
	auto swap2 = [a, b]() {
		int tmp = a;
		a = b;
		b = tmp;
		};

但是又不能修改左值,因为a,b只是拷贝。这时可以用到mutable:

	//使用mutable可以改变函数内的a,b
	auto swap2 = [a, b]() mutable {
		int tmp = a;
		a = b;
		b = tmp;
		};
	swap2();	//未发生改变
	cout << a << ":" << b << endl;

但是,a,b还是拷贝,结果还是不会变:
在这里插入图片描述

实际上是因为捕捉列表的捕捉方式不对。lambda只能用当前局部域和捕捉的成员
**默认是传值捕捉,并且使用的是const修饰的。mutable虽然去掉了const但是还是拷贝,不影响外部。

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用

  • [var]:表示值传递方式捕捉变量var (默认)
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针(在成员函数里会默认捕捉)

注意这里的&不是取地址,而是引用。

	int a = 1;
	int b = 2;
	cout << a << ":" << b << endl;

	//交换a,b
	auto swap2 = [&a,&b]() {
		int tmp = a;
		a = b;
		b = tmp;
		};
	swap2();
	cout << a << ":" << b << endl;
	auto swap3 = [&]() {
		int tmp = a;
		a = b;
		b = tmp;
		};
	swap3();
	cout << a << ":" << b << endl;

结果:
在这里插入图片描述
有如下几种用法:

	int a = 1, b = 2, c = 3, d = 4;
	//所有的值,传值捕捉(abcd)
	auto func1 = [=] {
		return a + b + c + d;
		};
	//所有的值,传引用捕捉(abcd)
	auto func2 = [&] {
		a++;
		b++;
		c++;
		d++;
		return a + b + c + d;
		};
	//混合捕捉
	auto func3 = [&a, b] {
		//a可以修改,影响外部a,
		//b不可修改,不影响外面
		};
	//除b之外的都传值捕捉
	auto func4 = [=, &b] {
		//b可修改,影响外部
		//其他不可修改
		};
	//。。。

lambda可以用全局域的变量。不需要捕捉
注意:
a. 父作用域指包含lambda函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
d. 在块作用域以外的lambda函数捕捉列表必须为空
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
f. lambda表达式之间不能相互赋值,即使看起来类型相同

void (*PF)();
int main()
{
	auto f1 = []{cout << "hello world" << endl; };
	auto f2 = []{cout << "hello world" << endl; };
	// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
	//f1 = f2; // 编译失败--->提示找不到operator=()
	// 允许使用一个lambda表达式拷贝构造一个新的副本
	auto f3(f2);
	f3();
	// 可以将lambda表达式赋值给相同类型的函数指针
	PF = f2;
	PF();
	return 0;
}

2.4 底层原理(函数对象与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表达式完全一样。
函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。
在这里插入图片描述
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()
虽说是匿名函数,但是编译器自己生成了一个uuid进行构造对象。
所以 lambda表达式之间不能相互赋值,即使看起来类型相同,是因为uuid不同。

三、包装器

3.1 function包装器

function包装器也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
在这里插入图片描述
它包含在<functional>头文件中,它可以包装任何类型的可调用对象(1、函数指针,2、仿函数、3、lambda)

那么我们来看看,我们为什么需要function呢?

#include <functional>
int f(int a,int b)
{
	return a + b;
}
struct Functor
{
	int operator()(int a, int b)
	{
		return a + b;
	}
};
int main()
{
	function<int(int, int)> f1 = f;
	function<int(int, int)> f2 = Functor();
	function<int(int, int)> f3 = [](int a, int b) {return a + b; };
	cout << f1(1, 1) << endl;
	cout << f2(1, 1) << endl;
	cout << f3(1, 1) << endl;
	return 0;
}

包装器的其他场景:
逆波兰表达式求值

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

  • 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
  • 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
class Solution
{
public:
	int evalRPN(vector<string>& tokens)
	{
		stack<int> st;
		for (auto& str : tokens)
		{
			//遇到符号计算
			if (str == "+" || str == "-" || str == "*" || str == "/")
			{
				int right = st.top();
				st.pop();
				int left = st.top();
				st.pop();
				switch (str[0])	//将结果压入栈
				{
				case '+':
					st.push(left + right);
					break;
				case '-':
					st.push(left - right);
					break;
				case '*':
					st.push(left * right);
					break;
				case '/':
					st.push(left / right);
					break;
				}
			}
			else//遇到数字入栈
			{
				// 1、atoi itoa
				// 2、sprintf scanf
				// 3、stoi to_string C++11
				st.push(stoi(str));
			}
		}
		return st.top();
	}
};

// 使用包装器以后的玩法
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
			{
				// 1、atoi itoa
				// 2、sprintf scanf
				// 3、stoi to_string C++11
				st.push(stoi(str));
			}
		}
		return st.top();
	}
};

注意function在包装类成员函数时,隐含的this指针要注意:

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	// 类的成员函数
	// 包装静态成员函数
	std::function<int(int, int)> func4 = &Plus::plusi;
	cout << func4(1, 2) << endl;
	// 包装普通成员函数
	std::function<double(Plus*, double, double)> func5 = &Plus::plusd;
	Plus pd;
	cout << func5(&pd, 1.1, 2.2) << endl;
	
	std::function<double(Plus, double, double)> func6 = &Plus::plusd;
	cout << func6(pd, 1.1, 2.2) << endl;
	cout << func6(Plus(), 1.1, 2.2) << endl;

	return 0;
}

有了包装器,如何解决模板的效率低下,实例化多份的问题呢?

#include <functional>
template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
	// 函数名
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表达式
	std::function<double(double)> func3 = [](double d)->double { return d / 4; };
	cout << useF(func3, 11.11) << endl;
	return 0;
}

3.2 bind

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。
在这里插入图片描述
在这里插入图片描述

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
调用bind的一般形式:auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的 callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数
arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1newCallable的第一个参数,_2为第二个参数,以此类推。
在这里插入图片描述
在这里插入图片描述
_N代表第N个实参。

// 使用举例
#include <functional>
int Plus(int a, int b)
{
	return a + b;
}
class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};
using placeholders::_1;
using placeholders::_2;
int main()
{
	//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
	std::function<int(int, int)> func1 = std::bind(Plus, _1,_2);
	//auto func1 = std::bind(Plus, _1, _2);

	//func2的类型为 function<void(int, int, int)> 与func1类型一样
	//表示绑定函数 plus 的第一,二为: 1, 2
	auto func2 = std::bind(Plus, 1, 2);
	cout << func1(1, 2) << endl;
	cout << func2() << endl;
	Sub s;
	
	//将第一个参数固定
	auto func3 = std::bind(Plus,1,_1)
	cout << func3(2) << endl; // 1 + 2
	
	// 绑定成员函数
	std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, _1, _2);

	// 参数调换顺序
	std::function<int(int, int)> func5 = std::bind(&Sub::sub, s, _2, _1);
	cout << func4(1, 2) << endl;
	cout << func5(1, 2) << endl;
	return 0;
}

如果你有所收获可以留下你的点赞和关注,谢谢你的观看!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1974475.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于JAVA的商品供应管理系统-JavaEE

点击下载源码 基于JAVA的商品供应管理系统-JavaEE 摘 要 当今社会己进入信息社会时代&#xff0c;信息己经受到社会的广泛关注&#xff0c;被看作社会和科学技术发展的三大支柱&#xff08;材料、能源、信息&#xff09;之一。信息是管理的基础&#xff0c;是进行决策的基本依…

vue3使用svg(无废话版)

1.去阿里云矢量图标库&#xff0c;复制svg代码 2.新建一个phone.svg文件(存放在assets/icons/phone.svg)&#xff0c;内容是刚刚复制的svg代码 <svg t"1722592653925" class"icon" viewBox"0 0 1024 1024" version"1.1" xmlns&quo…

数据失踪?这四款U盘数据恢复利器教你如何避免错误操作与保障安全性

当U盘上的数据不见了&#xff0c;数据恢复软件就像是你的救命稻草。一些好的数据恢复软件还会提供安全的恢复选项&#xff0c;确保在恢复的过程中不会对原来的数据造成损害。接下来&#xff0c;我们就来看看这些顶级的U盘数据恢复软件是怎么帮我们恢复U盘数据的&#xff0c;同时…

红旗E-QM5起火,一汽红旗否认车辆质量问题

近日&#xff0c;据媒体报道&#xff0c;7月31日下午&#xff0c;长春一辆一汽红旗E-QM5发生起火事故。 一汽红旗方面则表示&#xff1a;“现场勘查和初步调查表明&#xff0c;该事件并非因车辆自身质量问题导致自燃。疑似车辆在行驶过程中与路面井盖发生碰撞导致动力电池受损&…

专业130+总分430+浙大浙江大学842考研信号系统与数字电路经验电子信息与通信工程真题,大纲,参考书。

通过接近一年的备考&#xff0c;专业842信号和数电总结130&#xff08;专业课比预期低&#xff09;&#xff0c;总分430如愿上岸浙大&#xff0c;这一路复习走过弯路&#xff0c;淋过雨&#xff0c;也走过大路&#xff0c;风和日丽&#xff0c;总结一些自己的心得&#xff0c;希…

C语言 ——— 学习、使用 strcmp函数 并模拟实现

目录 strcmp函数的功能 学习strcmp函数​编辑 使用strcmp函数 模拟实现strcmp函数 strcmp函数的功能 strcmp函数的功能是字符串比较&#xff0c;两个字符串的对应位置的字符进行比较&#xff0c;直到字符不同或达到终止的 \0 字符为止 举例说明&#xff1a; 字符串1&am…

法制史学习笔记(个人向) Part.7

法制史学习笔记(个人向) Part.7 11. 清朝法律制度 11.1 立法概述 11.1.1 立法指导思想 简单来说是&#xff1a;详译明律&#xff0c;参以国制&#xff1b; 努尔哈赤时期&#xff0c;后金政权处于由习惯法到成文法的过渡过程中&#xff1b;皇太极统治时期&#xff0c;奉行“参…

Linux中vim的基本介绍和使用

善为理者&#xff0c;举其纲&#xff0c;疏其网。 vim 1、vim介绍2、命令模式详情3、底行模式详情4、困难问题5、历史存疑问题6、vim配置问题6、1、配置的原理6、2、一键式配置 1、vim介绍 如果我面想要在Linux上编写代码的话&#xff0c;我就需要vim来帮助我们编写代码。但是…

基于JSP、java、Tomcat三者的项目实战--校园交易网(3)主页--实现修改商品的名字与价格功能(万字爆更)增查改删,三端交互样样齐全

技术支持&#xff1a;JAVA、JSP 服务器&#xff1a;TOMCAT 7.0.86 编程软件&#xff1a;IntelliJ IDEA 2021.1.3 x64 前文几个功能的实现的博客 基于JSP、java、Tomcat、mysql三层交互的项目实战--校园交易网&#xff08;1&#xff09;-项目搭建&#xff08;前期准备工作&am…

Pycharm2023.1安装及其破解(含安装包)

一、下载 安装包 pycharm-professional-2023.1.exe https://www.alipan.com/s/f9WgrwLRbVn 提取码: 0yow 二、安装 三、激活 激活码&#xff1a;pycharm专业版激活码,2025.1月结束_pycharm序列号-CSDN博客 四、汉化

2024年8月2日(安装MySQL,以及各种操作)

一、安装并配置MySQL 1、下载mysql软件包 [rootmysql ~]# wget https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar 2、解压 [rootmysql ~]# tar -xf mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar 3、检查是否具有mariadb/mysql&…

技术成神之路:设计模式(十一)迭代器模式

前言 迭代器这个词听到并不陌生吧&#xff0c;我们再开发中遍历HashMap 和 HashSet的时候 用到的迭代器和这里的迭代器是一个概念&#xff0c;当然&#xff0c;这个模式不是教你如何去实现的&#xff0c;而是以了解为主。 介绍 迭代器模式&#xff08;Iterator Pattern&#…

携程实习生0506笔试-编程题

解题思路&#xff1a; 看坐标之和&#xff0c;奇数偶数不同字符即可。 解题思路&#xff1a; 题目应该是求连续的k长度最大值&#xff0c;遍历比较字符串&#xff0c;最后再取模。 import java.util.Scanner;public class Main {public static void main(String[] args) {Scann…

Keysight(原Agilent) E4980AL 精密 LCR 表特性与技术指标

Keysight(原Agilent) E4980AL 精密 LCR 表为基础 LCR 表树立了行业标准&#xff0c;可在多个频率范围内提供更佳的精度、速度和通用性。E4980AL 结合了种类繁多的附件&#xff0c;适用于一般研发和生产环境中的各种元件和材料测量。也可通过频率升级而提升投资回报率。 Keysig…

媲美Element Plus JuanTree终极实战:虚拟滚动

JuanTree组件功能迭代终于来到了终章——虚拟滚动实现。 有了前面学习的铺垫&#xff1a;vue大数据量列表渲染性能优化&#xff1a;虚拟滚动原理 把示例的实现用到JuanTree组件就变得非常的简单了。 功能视频演示&#xff1a; 自研Vue3 Tree组件 - 虚拟滚动功能演示 先来看效…

文档在线预览:keking/kkFileView踩坑记

文章目录 一、概述1、官方文档2、使用 二、部署服务1、传统部署方式&#xff08;1&#xff09;环境要求&#xff08;2&#xff09;生成部署包 2、docker部署方式 三、踩坑1、预览并发问题&预览首次打开慢2、字体问题乱码3、水印问题4、使用nginx代理5、docker部署指定配置 …

stm32 RAM for Algorithm问题,已解决

在此界面将 programming Algorithm内的flash remove&#xff0c;然后再重选就能解决

Drools

Drools基本了解 谈谈对业务规则管理系统的了解 举例说明 规则引擎系统是一个规则管理系统&#xff0c;接受数据输入、解释业务规则、根据业务规则做出业务决策的一个系统&#xff0c;其适用场景有贷款风险评估、积分优惠系统、保险理赔系统。规则是由“条件动作”组成&#x…

雅萌五代射频仪拆机图

雅萌五代射频仪拆机图&#xff0c;相对之前的版本&#xff0c;这个射频仪的头部电极部分&#xff0c;进行结构的优化&#xff0c;整个面比较平整光滑&#xff0c;不容易残留凝胶

allegro学习之一

1&#xff09;favorite设置界面 2&#xff09;双单位显示界面设置 3&#xff09;高亮实心显示 4&#xff09;自己设置的favorite 5&#xff09;导线圆滑显示 6&#xff09;plane的使用场景 7&#xff09;add to visibility的方法 8&#xff09;net group的使用 效果图如下&am…