【C++】30:C++11之lambda,新的类功能和包装器

目录

一、lambda

[1.1 lambda表达式语法](#1.1 lambda表达式语法)

[1.2 捕捉列表](#1.2 捕捉列表)

[1.3 lambda的应用](#1.3 lambda的应用)

[1.4 lambda的原理](#1.4 lambda的原理)

二、新的类功能

[2.1 默认的移动构造和移动赋值](#2.1 默认的移动构造和移动赋值)

[2.2 成员变量声明时给缺省值](#2.2 成员变量声明时给缺省值)

[2.3 default和delete](#2.3 default和delete)

[2.4 final和override](#2.4 final和override)

三、STL中一些变化

四、包装器

[4.1 function](#4.1 function)

[4.2 function的使用场景](#4.2 function的使用场景)

[4.3 bind](#4.3 bind)

[4.3.1 调整参数顺序(不常用)](#4.3.1 调整参数顺序(不常用))

[4.3.2 调整参数个数(常用)](#4.3.2 调整参数个数(常用))


一、lambda

1.1 lambda表达式语法

  • lambda表达式本质是一个匿名函数对象跟普通函数不同的是他可以定义在函数内部,lambda表达式语法使用层而言没有类型 ,所以我们一般是用auto或者模板参数定义的对象去接收lambda对象。
  • lambda表达式的格式:[capture-list] (parameters) -> return type{ function boby }
  • capture-list\]:捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据\[\]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用,捕捉列表可以传值和传引用捕捉,具体细节1.2中我们再细讲。**捕捉列表为空也不能省略**。

  • ->return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • { function boby }:函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略。
cpp 复制代码
#include<iostream>
using namespace std;

int main()
{
	auto add1 = [](int a, int b) ->int {return a + b; };
	cout << add1(1, 2) << endl;
	//1:捕捉为空也不能省略
	//2:参数为空可以省略
	//3:返回值可以省略,可以通过返回对象自动推导
	//4:函数体不能省略
	auto func1 = []
		{
			cout << "hello world" << endl;
		};
	func1();
	func1();

	int a = 1;
	int b = 2;
	cout << "交换前a=" << a << "b=" << b << endl;
	auto swap1 = [](int& a, int& b)
		{
			int tem = a;
			a = b;
			b = tem;
		};
	swap1(a, b);
	cout << "交换后a=" << a << "b=" << b << endl;
	return 0;
}

1.2 捕捉列表

  • lambda表达式中默认只能用lambda函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉
cpp 复制代码
#include<iostream>
using namespace std;

int main()
{
	//只能用当前lambda局部域和捕捉的对象和全局对象
	int a = 0, b = 1, c = 2, d = 3;
	auto func1 = [] (int x)
		{
			//值捕捉的变量不能修改,引用捕捉的变量可以修改
			a++;
			b++;
			int ret = a + b + x;
			return ret;
		};
	return 0;
}

以上代码是错误的,没有捕捉a和b,在函数体中是不能使用a和b的,可以使用参数x。

  • 第一种捕捉方式是在捕捉列表中显示的传值捕捉传引用捕捉捕捉的多个变量用逗号分割。[x,y,&z]表示x和y值捕捉,z引用捕捉。
cpp 复制代码
#include<iostream>
using namespace std;

int main()
{
	//只能用当前lambda局部域和捕捉的对象和全局对象
	int a = 0, b = 1, c = 2, d = 3;
	auto func1 = [a,&b](int x)
		{
			//值捕捉的变量不能修改,引用捕捉的变量可以修改
			//a++; 不能修改,因为是值捕捉
			b++;
			int ret = a + b + x;
			return ret;
		};
	cout << func1(1) << endl;
	return 0;
}
  • 第二种捕捉方式是在捕捉列表中隐式捕捉 ,我们在捕捉列表写一个=表示隐式值捕捉在捕捉列表写一个&表示隐式引用捕捉 ,这样我们lambda表达式中用了那些变量编译器就会自动捕捉那些变量
cpp 复制代码
#include<iostream>
using namespace std;


int y = 10;
int main()
{
	int a = 0, b = 1, c = 2, d = 3;
	auto func1 = [=](int x)
		{
			//a++; 不能修改,因为是值捕捉
			int ret = a + b + x + y;
			return ret;
		};
	auto func2 = [&](int x)
		{
			a++;
			b++;
			c++;
			d++;
			return x + a + b + c + d;
		};
	cout << func1(1) << endl;
	cout << func2(1) << endl;
	return 0;
}
  • 第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉[=,&x]表示其他变量隐式值捕捉,x引用捕捉;[&,x,y]表示其他变量引用捕捉,x和y值捕捉。当使用混合捕捉时,第一个元素必须是&或=,并且&混合捕捉时,后面的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必须是引用捕捉。
cpp 复制代码
#include<iostream>
using namespace std;


int y = 10;
int main()
{
	int a = 0, b = 1, c = 2, d = 3;
	auto func1 = [=,&c,&d](int x)
		{
			//a++; 不能修改,因为是值捕捉
			d++;
			c++;
			int ret = a + b + x + y;
			return ret;
		};
	auto func2 = [&,a,b](int x)
		{
			//a和b是值捕捉,其他都是引用捕捉,所以a和b不能修改,c和d可以修改
			//a++;
			//b++;
			c++;
			d++;
			return x + a + b + c + d;
		};
	cout << func1(1) << endl;
	cout << func2(1) << endl;
	return 0;
}
  • lambda表达式如果在函数局部域中,他可以捕捉lambda位置之前定义的变量不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉,lambda表达式中可以直接使用这也意味着lambda表达式如果定义在全局位置,捕捉列表必须为空
cpp 复制代码
#include<iostream>
using namespace std;


int y = 10;
int main()
{
	//只能用当前lambda局部域和捕捉的对象和全局对象
	int a = 0, b = 1, c = 2, d = 3;
	auto func1 = [a, &b](int x)
		{
			//值捕捉的变量不能修改,引用捕捉的变量可以修改
			//a++; 不能修改,因为是值捕捉
			b++;
			int ret = a + b + x + y;  //可以直接使用y,y是全局变量
			return ret;
		};
	cout << func1(1) << endl;
	return 0;
}
  • 默认情况下,lambda捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。
cpp 复制代码
#include<iostream>
using namespace std;


int y = 10;
int main()
{
	int a = 0, b = 1, c = 2, d = 3;
	auto func1 = [a, b,&c, &d](int x) mutable
		{
			//加了mutable之后,值捕捉的a和b可以修改了,但是这里修改a和b,不会影响外面的a和b
			a++; 
			b++;
			d++;
			c++;
			int ret = a + b + x + y;
			return ret;
		};
	cout << func1(1) << endl;
    cout << "a=" << a << ":b=" << b << endl;
	return 0;
}

1.3 lambda的应用

  • 在学习lambda表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义一个类,相对会比较麻烦。使用lambda去定义可调用对象,既简单又方便。
  • lambda在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等,lambda的应用还是很广泛的,以后我们会不断接触到。
cpp 复制代码
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
using namespace std;
struct Goods
{
	string _name;
	double _price;
	int _evaluate;

	Goods(const char* str, double price, int evaluate)
		:_name(str)
		,_price(price)
		,_evaluate(evaluate)
	{ }
};

struct compare1
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._price < g2._price;
	}
};
struct compare2
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._price > g2._price;
	}
};
int main()
{
	vector<Goods> v = { {"苹果",2.1,5},{"香蕉",2.7,7},{ "菠萝",2.9,10 },{"橙子",3.0,8} };
	//我们使用仿函数来对Goods来排序,但是从compare1和compare2的两个匿名对象来看
	// 并不知道是根据什么比较的,还需要找到定义仿函数的地方来看是根据什么比较的。
	sort(v.begin(), v.end(), compare1());
	sort(v.begin(), v.end(), compare2());

	//使用下面的lambda表达式,就可以清楚的看到是根据什么来排序的
	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; 
		});
	return 0;
}

1.4 lambda的原理

  • lambda的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有lambda和范围for这样的东西。范围for底层是迭代器,而lambda底层是仿函数对象,也就说我们写了一个lambda以后,编译器会生成一个对应的仿函数的类。
  • 仿函数的类名是编译按一定规则生成的,保证不同的lambda生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体,lambda的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是lambda类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传那些对象。
  • 上面的原理,我们可以透过汇编层了解一下,下面第二段汇编层代码印证了上面的原理。
cpp 复制代码
#include<iostream>
using namespace std;
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;

	// lambda
	auto r2 = [rate](double money, int year) {
		return money * rate * year;
		};

	// 函数对象
	Rate r1(rate);
	r1(10000, 2);

	r2(10000, 2);

	auto func1 = [] {
		cout << "hello world" << endl;
		};
	func1();

	return 0;
}

**//下面operator()**中才能使用

00D8295C lea eax ,[rate]

00D8295F push eax

00D82960 lea ecx ,[r2]

00D82963 call `main '::`2 ':::: (0D81F80h)11

**//**函数对象

Rate r1(rate);

00D82968 sub esp ,8

00D8296B movsd xmm0 ,mmword ptr [rate]

00D82970 movsd mmword ptr [esp],xmm0

00D82975 lea ecx ,[r1]

00D82978 call Rate ::Rate (0D81438h)

r1(10000 , 2);

00D8297D push 2

00D8297F sub esp ,8

00D82982 movsd xmm0 ,mmword ptr [__real@40c3880000000000 (0D89B50h)]

00D8298A movsd mmword ptr [esp],xmm0

00D8298F lea ecx ,[r1]

00D82992 call Rate ::operator() (0D81212h)26

**//汇编层可以看到r2 lambda对象调用本质还是调用operator(),类型是lambda_1 ,**这个类型名

//的规则是编译器自己定制的,保证不同的lambda****不冲突

r2(10000 , 2);

00D82999 push 2

00D8299B sub esp ,8

00D8299E movsd xmm0 ,mmword ptr [__real@40c3880000000000 (0D89B50h)]

00D829A6 movsd mmword ptr [esp],xmm0

00D829AB lea ecx ,[r2]

00D829AE call `main '::`2 '::::operator() (0D824C0h)

二、新的类功能

2.1 默认的移动构造和移动赋值

  • 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重载/const取地址重载,最后重要的是前4个,后两个用处不大,默认成员函数就是我们不写编译器会生成一个默认的。C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
  • 如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值

2.2 成员变量声明时给缺省值

  • C++11支持持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。

C++支持在成员变量声明的位置给缺省值,但是这个并不是成员变量的初始化,而是在没有显示在初始化列表初始化的成员使用的,代码如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
class A {
public:
    A():_a1(100),_a2(200) 
    {}
    void Print() {
        cout << "_a1=" << _a1 << endl;
        cout << "_a2=" << _a2 << endl;
        cout << "_a3=" << _a3 << endl;
    }
private:
    int _a1 = 1;
    int _a2 = 2;
    int _a3 = 3;
};
int main() {
    A a;
    a.Print();
    return 0;
}

运行输出如下所示:

在初始化列表中,我们给_a1,_a2这两个成员初始化了,就不会使用缺省值,但是我们没有给_a3这个成员初始化,所有这个变量就会使用在变量声明时写的默认值,

  • 尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。

2.3 default和delete

  • C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
  • 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
  • cin和cout类会将拷贝构造给delete,不允许拷贝。
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
#include<string.h>
#include<algorithm>
#include<list>
using namespace std;

namespace zx
{
	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);
		}
		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(string&& s)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);
		}
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 拷贝赋值" <<
				endl;
			if (this != &s)
			{
				_str[0] = '\0';
				_size = 0;
				reserve(s._capacity);
				for (auto ch : s)
				{
					push_back(ch);
				}
			}
			return *this;
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}
		~string()
		{
			//cout << "~string() -- 析构" << endl;
			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)
		{
			push_back(ch);
			return *this;
		}
		const char* c_str() const
		{
			return _str;
		}
		size_t size() const
		{
			return _size;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	};
}

class Person
{
public:
	Person(const char* name="",int age=0)
		:_name(name)
		,_age(age)
	{ }
	Person(const Person& p)
		:_name(p._name)
		,_age(p._age)
	{ }

	Person(Person&& p) = default;

	//Person(const Person& p) = delete;

private:
	zx::string _name;
	int _age;
};

int main()
{
	Person s1;
	Person s2  = s1;
	Person s3 = move(s1);
	return 0;
}

2.4 final和override

C++对虚函数重写的要求比较严格,但是有些情况下由于疏忽,比如函数名写错参数写错等导致无法构成重写,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此C++11提供了override,可以帮助用户检测是否重写。如果我们不想让派生类重写这个虚函数,那么可以用final去修饰.

如下所示:我们想要实现多态,在重写的时候函数名写错了,就不构成重写,因此不构成多态。

cpp 复制代码
#include<iostream>
using namespace std;
class Car {
public:
	virtual void Dirve()
	{
	}
};
class Benz :public Car {
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};
int main()
{
	return 0;
}

这种情况编译的时候检查不出来,在运行的时候发现没有达到多态的效果,然后检查函数,才发现是函数名写错了,没有达到重写的条件。

C++11提供了override,可以帮助用户检测是否重写,在函数名后就加上override 如果没有重写,就会报错。如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
class Car {
public:
	virtual void Dirve()
	{
	}
};
class Benz :public Car {
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};
int main()
{
	return 0;
}

如果我们不想让派生类重写这个虚函数,那么可以用final去修饰,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
class Car
{
public:
	virtual void Drive() final {}
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};
int main()
{
	return 0;
}

三、STL中一些变化

  • 下图圈起来的就是STL中的新容器,但是实际最有用的是unordered_map和unordered_set。这两个我们前面已经进行了非常详细的讲解,其他的大家了解一下即可。
  • STL中容器的新接口也不少,最重要的就是右值引用和移动语义相关的push/insert/emplace系列接口和移动构造和移动赋值,还有initializer_list版本的构造等,这些前面都讲过了,还有一些无关痛痒的如cbegin/cend等需要时查查文档即可。
  • 容器的范围for遍历,这个在容器部分也讲过了。

四、包装器

4.1 function

template <class T>

class function; // undefined

template <class Ret , class ...Args>

class function<Ret(Args ...)>;

  • std::function是一个类模板,也是一个包装器。std::function的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、lambda、bind表达式等,存储的可调用对象被称为std::function的目标。若std::function不含目标,则称它为空。调用空std::function的目标导致抛出std:bad_function_call异常。
  • 以上是function的原型,他被定义<functional>头文件中。std::function - cppreference.com是function的官方文件链接。
  • 函数指针、仿函数、lambda等可调用对象的类型各不相同,std::function的优势就是统一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型,下面4.2 function的使用场景样例展示了std::function作为map的参数,实现字符串和可调用对象的映射表功能。
cpp 复制代码
#include<iostream>
#include<functional>
using namespace std;

int f(int a, int b)
{
	return a + b;
}
struct functor
{
	int operator()(int a, int b)
	{
		return a + b;
	}
};

class Plus
{
public:
	Plus(int n = 10)
		:_n(n)
	{}

	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return (a + b) * _n;
	}
private:
	int _n;
};
int main()
{
	//包装各种可调用对象
	//包装全局函数
	function<int(int, int)> f1 = f;  
	//包装仿函数
	function<int(int, int)> f2 = functor();
	//包装lambda
	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;

	//包装类的成员函数 需要指定类域并且前面加&才能获取地址
	//1:包装类的成员函数,还需要传入一个对象指针或者对象,因为非静态成员函数有this指针,类型会对不上。
	function<double(Plus*,double, double)> f4 = &Plus::plusd;
	Plus p1;
	cout << f4(&p1,1.11, 2.22) << endl;

	function<double(Plus, double, double)> f6 = &Plus::plusd;
	cout << f6(p1, 1.1, 1.1) << endl;
	cout << f6(p1, 1.1, 1.1) << endl;

	function<double(Plus&&, double, double)> f7 = &Plus::plusd;
	cout << f7(move(p1), 1.1, 1.1) << endl;
	cout << f7(Plus(), 1.1, 1.1) << endl;

	//2:包装类的静态成员函数,可以加上&,也可以不用加,还不需要传入一个对象指针
	function<int(int, int)> f5 = &Plus::plusi;
	cout << f5(1, 2) << endl;
	return 0;
}

4.2 function的使用场景

我们先来看一道算法题目:150. 逆波兰表达式求值 - 力扣(LeetCode)

传统方式实现如下所示:

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(size_t i=0;i<tokens.size();i++)
        {
            string ch=tokens[i];
            if(ch=="+"||ch=="-"||ch=="*"||ch=="/")
            {
                int right=st.top();
                st.pop();
                int left=st.top();
                st.pop();
                if(ch=="+")
                {
                    st.push(left+right);
                }else if(ch=="-")
                {
                    st.push(left-right);
                }else if(ch=="*")
                {
                    st.push(left*right);
                }else if(ch=="/")
                {
                    st.push(left/right);
                }
            }else
            {
                st.push(stoi(tokens[i]));
            }
        }
        return st.top();
    }
};

c++11方式实现如下所示:

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        map<string,function<int(int,int)>> opFuncMap=
        {
            {"+",[](int x,int y){return x+y;}},
            {"-",[](int x,int y){return x-y;}},
            {"*",[](int x,int y){return x*y;}},
            {"/",[](int x,int y){return x/y;}}
        };
        for(auto& str:tokens)
        {
            if(opFuncMap.count(str))
            {
                int right=st.top();
                st.pop();
                int left=st.top();
                st.pop();
                int ret=opFuncMap[str](left,right);
                st.push(ret);
            }else
            {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

方式2借用了map,包装器和lambda来解决这个问题。map的key是string,map的value是包装器。包装器可以包装一切可调用对象(包括函数指针、仿函数、lambda、bind表达式)。

如果有100个操作类型呢?我们使用方式1就需要写100个if-else语句,而在在方式2中,只需要在定义map的时候,将操作和方法存到map里面就可以了。

function作为可调用对象的类型会非常方便。想用函数指针、仿函数、lambda、bind表达式都可以用function来接受。

4.3 bind

simple(1)

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是一个函数模板,它也是一个可调用对象的包装器,可以把他看做一个函数适配器,对接收的fn可调用对象进行处理后返回一个可调用对象。bind可以用来调整参数个数和参数顺序。bind也在<functional>这个头文件中。
  • 调用bind的一般形式:auto newCallable =bind(callable,arg_list);其中newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
  • arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。_1/_2/_3..这些占位符放到placeholders的一个命名空间中。

4.3.1 调整参数顺序(不常用)

cpp 复制代码
#include<iostream>
#include<functional>
using namespace std;

using placeholders::_1;
using placeholders::_2;
using placeholders::_3;

int Sub(int a, int b)
{
	return (a - b) * 10;
}

int main()
{
	auto sub1 = bind(Sub, _1, _2);
	cout << sub1(10, 5) << endl;
	//bind本质返回一个仿函数对象
	//调整参数顺序(不常用)
	//_1表示第一个形参
	//_2表示第二个形参
	auto sub2 = bind(Sub, _2, _1);  //将参数顺序给调整了 _1表示10,_2表示5
	cout << sub2(10, 5) << endl;
	return 0;
}

4.3.2 调整参数个数(常用)

cpp 复制代码
#include<iostream>
#include<functional>
using namespace std;

using placeholders::_1;
using placeholders::_2;
using placeholders::_3;

int Sub(int a, int b)
{
	return (a - b) * 10;
}

int SubX(int a, int b, int c)
{
	return (a - b - c) * 10;
}
class Plus
{
public:
	Plus(int n = 10)
		:_n(n)
	{}

	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return (a + b) * _n;
	}
private:
	int _n;
};
int main()
{
	auto sub1 = bind(Sub, 100, _1);  //将a给固定死了,a始终是100;
	cout << sub1(5) << endl;

	auto sub2 = bind(Sub, _1, 100);  //将b给固定死了,b始终是100;
	cout << sub2(5) << endl;

	//分别绑死第1,2,3个参数
	auto sub3 = bind(SubX, 100, _1, _2);
	cout << sub3(5, 1) << endl;
	auto sub4 = bind(SubX, _1, 100, _2);
	cout << sub4(5, 1) << endl;
	auto sub5 = bind(SubX, _1, _2, 100);
	cout << sub5(5, 1) << endl;

	// 成员函数对象进行绑死,就不需要每次都传递了
	function<double(Plus&&, double, double)> f6 = &Plus::plusd;
	Plus pd;
	cout << f6(move(pd), 1.1, 1.1) << endl;
	cout << f6(Plus(), 1.1, 1.1) << endl;

	// bind一般用于,绑死一些固定参数
	function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
	cout << f7(1.1, 1.1) << endl;

	auto func1 = [](double rate, double money, int year)->double
		{
			double ret = money;
			for (int i = 0; i < year; i++)
			{
				ret += ret * rate;
			}
			return ret - money;
		};
    // 绑死一些参数,实现出支持不同年华利率,不同金额和不同年份计算出复利的结算利息
	function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
	function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
	function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
	
	cout << func3_1_5(1000000) << endl;
	cout << func5_1_5(1000000) << endl;
	cout << func10_2_5(1000000) << endl;

	return 0;
}
相关推荐
fengenrong1 小时前
APIO2026游记
c++
会开花的二叉树1 小时前
从 C++ 转向 AI 应用工程:我的 Python 基础第一阶段复盘
c++·人工智能·python
Cx330❀1 小时前
从零实现一个 C++ 轻量级日志系统:原理与实践
大数据·linux·运维·服务器·开发语言·c++·搜索引擎
程序leo源1 小时前
Linux深度理解
linux·运维·服务器·c语言·c++·青少年编程·c#
计算机安禾1 小时前
【c++面向对象编程】第7篇:static成员:属于类而不是对象的变量和函数
java·c++·算法
影sir1 小时前
STL容器——list类
c++·链表·stl·list
ooseabiscuit1 小时前
PHP与C++:Web与系统编程的终极对决
前端·c++·php
艾莉丝努力练剑1 小时前
【Linux网络】Linux 网络编程:应用层自定义协议与序列化(3):网络计算器实现和守护进程
linux·运维·服务器·网络·c++·计算机网络·安全
千寻girling1 小时前
周日那天参加的力扣周赛... —— 10号
java·javascript·c++·python·算法·leetcode·职场和发展