当前位置: 首页 > article >正文

【C++进阶】之C++11的简单介绍(三)

在这里插入图片描述

📃博客主页: 小镇敲码人
💚代码仓库,欢迎访问
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞

【C++进阶】之C++11的简单介绍(三)

  • lambda表达式
    • 为什么要有lambda表达式
    • lambda表达式解决上述问题
    • lambda表达式的语法
      • lambda表达式中值捕获与引用捕获的区别
    • lambda表达式在类函数作用域中捕获this对象(非静态函数)
      • 深入理解值捕获lambda的常性与类里面的常量成员函数的区别
        • 再谈类和对象中的this指针
    • lambda表达式的底层原理
      • 不同的lambda表达式不能互相赋值
  • 包装器
    • 包装器引入的初衷
    • 包装器的语法
      • 包装器介绍
        • 包装器包装类成员函数的不同方式
      • 使用包装器解决上述问题
    • 包装器的其它应用
    • 绑定
      • 绑定修改可调用对象的参数个数
      • 绑定修改可调用对象的参数的调用顺序

lambda表达式

为什么要有lambda表达式

库里面有一个排序函数sort,它的接口如下:
在这里插入图片描述

前两个参数是要排序的容器的起始迭代器,最后面一个是比较规则,它是一个实例化后的对象,可以是类对象也可以是函数指针。

如果我们要排的是内置类型:

#include<algorithm>
#include<vector>
#include<iostream>
#include<time.h>

using namespace std;


template<class T>
void print(T& v)
{
	for (auto& num : v)
		cout << num << " ";
	cout << endl;
}
int main()
{
	srand(time(NULL));
	vector<int> v;

	for (int i = 0; i < 10; ++i)
		v.emplace_back(i);


	
	
	sort(v.begin(), v.end(), greater<int>());

	print(v);
	return 0;
}

运行结果:

在这里插入图片描述
对内置类型排序可以使用类里面的比较函数,传greater<T>对象就是降序,传less<T>对象就是升序,但是加入我们要对日期类排序呢?就必须自己写一个仿函数或者函数指针对象传进去了。

#include<algorithm>
#include<vector>
#include<iostream>
#include<time.h>

using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		//cout << "Date(int year, int month, int day)" << endl;
	}

	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		//cout << "Date(const Date& d)" << endl;
	}

	friend ostream& operator<<(ostream& is, const Date& date);
	friend int compare(const Date& a, const Date& b);
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

ostream& operator<<(ostream& os, const Date& date)
{
	os << date._year << " " << date._month << " " << date._day;
	return os;
}
template<class T>
void print(T& v)
{
	for (auto& num : v)
		cout << num <<   endl;
	
}

int compare(const Date& a, const Date& b)
{
	return a._year > b._year || (a._year == b._year && a._month > b._month) || (a._year == b._year && a._month == b._month && a._day > b._day);
}
int main()
{
	srand(time(NULL));
	vector<Date> v;

	for (int i = 0; i < 10; ++i)
		v.emplace_back(i+rand(),rand()%13,rand()%31);



	print(v);
	cout << "-----------------------------------------" << endl;
	sort(v.begin(), v.end(), compare);

	print(v);
	return 0;
}

运行结果:

在这里插入图片描述
也可以创建一个仿函数对象传进去。

这样做太复杂了,我们只需要实现一个排序的功能,就要去写一个函数或者是一个类,如果每次排序逻辑不一样,岂不是每次都要多写一个类,这给程序员们带来了极大的不便,所以lambda表达式出现了。

lambda表达式解决上述问题

#include<algorithm>
#include<vector>
#include<iostream>
#include<time.h>

using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		//cout << "Date(int year, int month, int day)" << endl;
	}

	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		//cout << "Date(const Date& d)" << endl;
	}

	friend ostream& operator<<(ostream& is, const Date& date);
	friend int compare(const Date& a, const Date& b);
public:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

ostream& operator<<(ostream& os, const Date& date)
{
	os << date._year << " " << date._month << " " << date._day;
	return os;
}
template<class T>
void print(T& v)
{
	for (auto& num : v)
		cout << num <<   endl;
	
}


int main()
{
	srand(time(NULL));
	vector<Date> v;

	for (int i = 0; i < 10; ++i)
		v.emplace_back(i+rand(),rand()%13,rand()%31);



	print(v);
	cout << "-----------------------------------------" << endl;
	sort(v.begin(), v.end(),[](const Date& a,const Date& b)
		{
		return a._year > b._year || (a._year == b._year && a._month > b._month) || (a._year == b._year && a._month == b._month && a._day > b._day);
		}
		);

	print(v);
	return 0;
}

运行结果:

在这里插入图片描述

lambda表达式的语法

从上面的代码可以看出lambda表达式是一个匿名函数,下面让我们具体学习它的语法:
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

  • [capture-list]:捕捉列表,该表达式总是出现在开始位置,编译器通过捕捉列表判断接下来的代码是否是lambda表达式,捕捉列表能够捕捉上下文的变量供lambda函数使用。(同一作用域函数类的变量或者是全局的变量或类型)。
  • parameters):参数列表,该表达式内需想一般函数一样写出lambda函数的形参列表。如果是无参函数,可以和()一起省略。
  • mutable:在lambda表达式中,如果捕获列表中的变量是捕获的,并且你希望在lambda函数体内修改这些变量的值,你需要在lambda定义时加上mutable关键字。这允许lambda函数修改其捕获的变量。
  • ->returntyp没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
    导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量。

lambda表达式中值捕获与引用捕获的区别

  1. 值捕获

当你在lambda表达式中值捕获了一个变量,这个函数是默认具有常性的,也就是你值捕获的变量都不可修改,带有const修饰符,这样的设计的初衷可能是认为既然你值捕获了一个变量,肯定是只希望准确的使用它,所以修改是无意义的(而且修改后可能会导致我们的结果出现错误),所以lambda表达式默认给它带const属性。

  1. 值捕获是对原变量的拷贝(地址不同):
#include<iostream>

using namespace std;

int main()
{
	int b = 0;
	cout << &b << endl;

	auto Func = [b] 
	{
		cout << &b << endl; 
		const int& b_ = b;
	};

	Func();
	return 0;
}

运行结果:

在这里插入图片描述
const int 是它的类型,如果你把引用变量的类型的const属性丢掉,就无法对其引用:

在这里插入图片描述

  1. 添加mutable修饰符,去掉值捕获变量的const属性,添加mutable后参数列表的()不能省略,即使它的参数为空:
#include<iostream>

using namespace std;

int main()
{
	int b = 0;
	cout << &b << endl;

	auto Func = [b] () mutable
	{
		b = 3;
		cout << &b << endl;
	};
	Func();

	cout << b << endl;
	return 0;
}

在这里插入图片描述
这里添加了mutable修饰符后我们对lambda函数中捕获的b可以修改了,但是由于是值捕获,并无法影响外面。

  1. lambda值捕获一个变量后,类型和外面的被捕获变量保持一致,即使是const修饰符也会一致(&不会)。

在这里插入图片描述
2.引用捕获

引用捕获的变量,在lambda表达式中,不会给它加上const属性,所以mutable对引用捕获的变量无影响,因为我们本意就是想修改它们,如果默认加上const属性就无意义了。

#include<iostream>

using namespace std;

int main()
{
	int b = 0;
	
	auto Func = [&b] ()
	{
		b = 3;
		cout << &b << endl;
	};
	Func();

	cout << b << endl;
	return 0;
}

运行结果:

在这里插入图片描述

捕获列表的语法:

  • []:空捕获,不捕获任何变量。
  • [var]:值捕获var变量。
  • [&var]:引用捕获var变量。
  • [=]:值捕获所有变量,包括this类中的指针。(在父作用域内或者全局中可访问的变量)
  • [&]:引用捕获所有变量。包括this指针。(在父作用域内或者全局中可访问的变量)
  • [=,&var]:值捕获其它变量,引用捕获var变量。
  • [&,var]:引用捕获其它变量,引用捕获var变量。

注意:

  • 捕捉列表不能捕获重复的变量否则会报错。

在这里插入图片描述

  • 父作用域指包括lambda表达式的语句块。

lambda表达式在类函数作用域中捕获this对象(非静态函数)

#include <iostream>  
#include <functional>  
using namespace std;

class MyClass {
public:
    int value;

    void doSomething() {
        // 使用lambda表达式,并在内部直接使用this指针  
        auto lambda = [this]() {
            std::cout << "Accessing member via this: " << this->value << std::endl;
            // 也可以直接访问,无需this->,如:std::cout << value << std::endl;  
        };

        // 调用lambda  
        lambda();
        std::cout << value << std::endl;
        // 修改成员变量,并通过lambda再次访问以验证修改  
        value = 42;
        lambda();
    }
};




int main() {
    MyClass obj;
    obj.value = 10;
    obj.doSomething();
    return 0;
}

运行结果:

在这里插入图片描述

深入理解值捕获lambda的常性与类里面的常量成员函数的区别

#include <iostream>  
#include <functional>  
using namespace std;

class MyClass {
public:
    int value;

    void doSomething() {
        // 使用lambda表达式,并在内部直接使用this指针  
        auto lambda = [this]() {
            std::cout << "Accessing member via this: " << this->value << std::endl;
            this->value = 2;
            // 也可以直接访问,无需this->,如:std::cout << value << std::endl;  
        };

        // 调用lambda  
        lambda();
        std::cout << value << std::endl;
    }

    void Func() const//const MyClass * const this
    {
        //Func为常量函数,不可修改
        //value = 3;
    }
};




int main() {
    MyClass obj;
    obj.value = 10;
    obj.doSomething();
    return 0;
}

运行结果:

在这里插入图片描述

为什么lambda表达式使用的是值捕获,还是可以修改this指针指向的内容呢?但是常量成员函数却不能修改:

  • lambda表达式的const修饰的是变量本身,而不是变量指向的内容,在这个例子中也就是:MyClass * const this,const修饰的是this变量本身,它本身保存的地址不能修改,但是它指向的内容可以修改,这就是我们可以修改它的成员的原因,值捕获是拷贝了和this指针一样的地址。
  • const修饰成员函数则不同,它修饰的是this指针指向的内容,这点我们在类和对象部分讲过,在本例子中也就是:MyClass const * const this,但是由于this指针变量本身就具有常量属性(它是一个右值,我们稍后会证明)。
再谈类和对象中的this指针

this指针是一个右值,具有常性。我们无法对右值取地址。

class MyClass {
public:
    int value;

    void Func() //const MyClass * const this
    {
         cout << &this << endl; //this指针是一个右值无法取地址
    }
};

报错截图:

在这里插入图片描述

当我们使用const修饰成员函数时,即使使用右值引用引用this指针,也只能做到修改右值引用变量本身,无法修改它指向的内容。这是因为const修饰的是*this,也就是this指针指向的内容。我们上面在右值引用引用指针部分已经谈到了。

class MyClass {
public:
    int value;

    void Func() const//const MyClass * const this
    {
        MyClass const* const &lptr = this;//少一个const都无法使用左值引用
        MyClass const* ptr = this;//不是引用,指针变量可以不使用const修饰,但是它指向的内容必须用const修饰
        MyClass const*&& rptr = this;//const不修饰*rptr就会报错
    }
};

只要在成员函数中就可以访问本类的this指针,都可以省略->或者.访问符号。(特指lambada表达式在类成员函数时)

#include <iostream>  
#include <functional>  
using namespace std;

class MyClass {
public:
    int value;

    void doSomething() {
        // 使用lambda表达式,并在内部直接使用this指针  
        auto lambda = [this]() {//MyClass * const this
            std::cout << "Accessing member via this: " << value << std::endl;
            value = 2;
            // 也可以直接访问,无需this->,如:std::cout << value << std::endl;  
        };

        // 调用lambda  
        lambda();
        std::cout << value << std::endl;
    }
};

int main() {
    MyClass obj;
    obj.value = 10;
    obj.doSomething();
    return 0;
}

但是有重名的情况还是不能省略,不然可能出现未定义行为。友元函数和友元类中不能这样做,因为它们只是拥有访问我们类私有成员的权利,并不直接属于类域。

运行结果:

在这里插入图片描述

引用捕获this指针和值捕获区别不大(由于this指针是右值,无法修改),我们这里不再谈到。

lambda表达式的底层原理

lambda表达式是借助仿函数实现的。

#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;
	Rate r1(rate);
	r1(10000, 2);

	// lambda
	auto r2 = [=](double monty, int year)->double {return monty * rate * year;};
	r2(10000, 2);

	auto f1 = [] {cout << "hello world" << endl; };
	auto f2 = [] {cout << "hello world" << endl; };

	f1();
	f2();

	return 0;
}

反汇编查看其底层调用:

在这里插入图片描述

  • 从反汇编的底层调用中可以看出,lambda表达式其实就是一个匿名的仿函数类,只能创建一次,因为再次创建的时候你无法知道其类名称,也就无法再创建对象了。我们通过auto自动推导类型的功能,创建了一个仿函数对象,调用的时候是调用其底层的operator()函数。

这里虽然f1f2的函数体一样,但是它们仍然是两个不同类型的lambda表达式。

在这里插入图片描述

其实我们通过decltype关键字也可以实现再次创建相同类型的lambda表达式。

#include<iostream>
using namespace std;

int main()
{
	double rate = 0.49;
	// lambda
	auto r1 = [=](double monty, int year)->double {return monty * rate * year; };
	decltype(r1) r2 = r1;//支持赋值运算符重载
	decltype(r1) r3(r2);//支持拷贝构造
	//decltype(r1) r4;禁掉了 默认构造

	cout << typeid(r1).name() << endl;
	cout << typeid(r2).name() << endl;
    cout << typeid(r3).name() << endl;

	return 0;
}

运行结果:

在这里插入图片描述

不同的lambda表达式不能互相赋值

在介绍完lambda表达式的原理之后想必就很好理解了,两个仿函数对象连类型都不同,怎么能互相赋值呢。

#include<iostream>
using namespace std;

int main()
{

	auto f1 = []()
	{
		cout << "我是lambada表达式" << endl;
	};

	auto f2 = [=]()
	{
		cout << "我是lambada表达式" << endl;
	};

	//f2 = f1;这是不正确的,会报错

	return 0;
}

在这里插入图片描述

但是可以将lambda对象赋值给同类型的函数指针(参数和返回值一样)。前提是这个lambda对象没有捕获外部变量(如果它捕获了外部变量,则单纯的函数无法表示这种状态)。此时我们可以认为lambda表达式的行为和函数没有区别,但是底层还是一个类。

在这里插入图片描述

  • 我们可以看到,即使f1没有捕获任何外部变量,打印出它的类型还是一个自定义的类,至于它是如何赋值给符合要求的函数指针的,我们就不得而知了。
#include<iostream>
using namespace std;

typedef void (*pf) ();

class MyClass
{
public:
	void operator()()
	{
		cout << "我是仿函数对象" << endl;
	}
};
int main()
{
	int a = 0;
	auto f1 = []()
	{
		cout << "我是lambada表达式" << endl;
	};

	f1();
	auto f2 = [=]()
	{
		cout << a << endl;
		cout << "我是lambada表达式" << endl;
	};

	f2();
	MyClass f3;
	pf Pf1 = f1;//可以赋值

	//pf Pf2 = f2;
	return 0;
}

如果把f2赋值给同类型的函数指针变量就会报错,因为它依赖外部变量,函数无法表示这种状态,此时可以把lambda表达式看作一个仿函数对象。

在这里插入图片描述

包装器

包装器引入的初衷

我们已经学习了仿函数、lambda表达式和函数指针,它们三个都可以去像函数一样使用。但是传参上有各有不同,特别是函数指针,在类里面还不能通过传类型来定义,必须传函数指针给类方法,很不方便;lambda表达式更是连类型我们都不能直观的看见。这不是最主要的,最主要的是,它们三个的存在会让模板的效率变得低下。

看下面的代码:

#include<iostream>

using namespace std;

template<class F,class T>
T Functor(F f,T data)
{
	static int count = 0;
	count++;
	cout << count << endl;
	cout << &count << endl;
	return f(data);
}


class  MyClass
{
public:
	double operator()(double data)
	{
		return data / 2;
   }
};


double func1(double x)
{
	return x * 3;
}

int main()
{
	MyClass fun;
	Functor(fun, 3.4);
	Functor(func1, 10.9);
	Functor([](double x)-> double {return x / 4; }, 2.2);

	return 0;
}

运行结果:

在这里插入图片描述

如果调用的是同一份模板函数,count是静态成员,应该只会定义一次。但运行结果显示count定义了三次,所以函数模板实例化了三份。

能否存在一种机制,可以让我们不区分它们三个,让它们的使用方式一样呢?于是C++11引入了包装器的语法,也叫function

包装器的语法

包装器介绍

包装器是functional头文件中封装的一个类模板。

在这里插入图片描述

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

Ret是函数的返回值,Args是参数包,也就是函数的一系列参数。
我们只需要了解包装器的使用即可,对于其底层不用过多研究。

下面介绍function的各种使用场景:

#include<functional>
#include<iostream>

using namespace std;
//函数
int f1(int a, int b)
{
	return a + b;
}

//仿函数对象
class f2
{
public:
	int operator()(int a, int b)
	{
		return a + b;
	}
};

//普通类

class f3
{
public:
	static int f1(int a, int b)
	{
		return a + b;
	}

	int f2(int a, int b)
	{
		return a + b;
	}
};

int main()
{
	function<int(int, int)> func1 = f1;//包装函数
	cout << func1(1, 2) << endl;

	//包装仿函数对象两种方式
	function<int(int, int)> func2 = f2();//方式1

	function<int(f2, int, int)> func3 = &f2::operator();//它的底层我们不关心如何实现

	cout << func2(1, 2) << endl;
	cout << func3(f2(),1, 2) << endl;

	//包装类中的静态函数
	function<int(int, int)> func4 = &f3::f1;

	//包装类中的普通成员方法
	function<int(f3, int, int)> func5 = &f3::f2;

	cout << func4(1, 2) << endl;
	cout << func5(f3(), 1, 2) << endl;
	return 0;
}

运行结果:

在这里插入图片描述

包装器包装类成员函数的不同方式

对于其第一个参数(类对象)支持不同传参方式

#include<iostream>
#include<functional>

using namespace std;

class MyClass
{
public:
   int Add(int a, int b)
   {
   	return a + b;
   }
};

int main()
{
   包装类中的方法的几种方式
   //1.第一个参数是类指针
   function<int(MyClass*, int, int)> func1 = &MyClass::Add;//但是这里必须带取地址
   MyClass object;
   cout << func1(&object, 3, 2) << endl;

   //2.第一个参数是类的右值引用
   function<int(MyClass&&, int, int)> func2 = &MyClass::Add;
   cout << func2(MyClass(), 3, 2) << endl;


   int num = 3;
   //3.第一个参数是普通类对象
   function<int(const MyClass,int,int)> func3 = &MyClass::Add;
   cout << func3(MyClass(),3, 2) << endl;

   //4.第一个参数是左值引用
   function<int(MyClass&, int, int)> func4 = &MyClass::Add;
   cout << func4(object, 3, 2) << endl;

   return 0;
}

运行结果:

在这里插入图片描述

在使用function的时候,我们还发现function中的类型应该尽量和原调用对象的类型保持一致,(特别是const的有无)。不然就很可能出现一些问题:

在这里插入图片描述

这里我们将MyClass对象的左值引用加上const修饰就报错了,也能理解因为函数方法不具有常性(没有加const修饰),我们使用function封装,最终肯定也要回调原可调用对象(这里就是类中的成员方法),会涉及强制类型转化。这个成员方法没有加const修饰,它的this指针指向的内容是可修改的,const &->&会报错我们可以理解。

但是下面这种情况呢?

我们给非引用的MyClass对象加上const修饰传进去,它没有报错。

再看下面的代码,你就会发现这并不是偶然:

#include<iostream>
#include<functional>

using namespace std;

class MyClass
{
public:
	int Add(int& a, int b)
	{
		return a + b;
	}
};

int main()
{
	int num1 = 2;
	function<int(MyClass&&, int&, const int)> func2 = &MyClass::Add;
	cout << func2(MyClass(),num1, 2) << endl;


	function<int(const MyClass, const int&,int)> func3 = &MyClass::Add;
	cout << func3(MyClass(), 4, 2) << endl;


	return 0;
}

报错截图:

在这里插入图片描述

MyClass对象的传参太特殊了,不能说明问题。那现在普通的参数呢?当我们的函数方法中的第一个参数是int&const int&->int &是无法强制类型转换成功的,但是const intint只是值传递可以转成功,这和我们之前学的知识是逻辑自洽的。至于类中的第一个参数this指针具体在function中是如何处理的,我们就不得而知了。

  • 总结:在使用function包装调用对象时,尽可能的不要加const修饰符,如果你对这方面的语法不是很熟悉的话(int &可以->const int&,int*可以->const int* 。但是反过来就不行。==(等价于) 权限可以缩小,但是不能放大)。也可以选择和原调用对象的参数保持严格的一致。

通过这里对function类的学习,我们对this指针又产生了一个疑惑,到底是function内部如此处理,还是类和对象中这样去处理的呢?为什么对于类中的方法的第一个参数,能有如此多的传参方式。但是不管其底层如何,我们只要明确一点,在function这里就不会出错,函数方法没有const修饰的,不要给引用变量本身或者指针指向的内容加上const修饰符

使用包装器解决上述问题

代码如下:

#include<iostream>
#include<functional>
using namespace std;

template<class F,class T>
T Functor(F f,T data)
{
	static int count = 0;
	count++;
	cout << count << endl;
	cout << &count << endl;
	return f(data);
}


class  MyClass
{
public:
	double operator()(double data)
	{
		return data / 2;
   }
};


double func1(double x)
{
	return x * 3;
}

int main()
{
	function<double(double)> func1_ = MyClass();
	Functor(func1_, 2.2);

	function<double(double)> func2_ = func1;
	Functor(func2_, 2.2);

	function<double(double)> func3_ = [](double x)->double {return x / 4; };
	Functor(func3_, 10.9);

	return 0;
}

运行结果:

在这里插入图片描述

  • function模板类的模板参数一样,所以它们三个是一样的类型(对于类模板来说,实例化后的模板类的类型是类模板带上模板参数)。仿函数的function类型比较特殊(相比于类中的普通方法),是为了便于与函数和lambda表达式调用方式统一。lambda表达式和仿函数的function封装方式是最相似的,都是把一个创建好的对象赋值给function。这与它们的底层有一定的关系。

包装器的其它应用

看下面一到OJ题,思考如何使用包装器来做。这里是题目链接

题干如下:

在这里插入图片描述

ak代码:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
    stack<int> st1;
    map<string,function<int(int,int)>> mp1 = {{"+",[](int a,int b)->int{return a+b;}},
                                              {"*",[](int a,int b)->int{return a*b;}},
                                              {"-",[](int a,int b)->int{return a-b;}},
                                              {"/",[](int a,int b)->int{return a/b;}}
                                              };
    for(auto& str:tokens)
    {
        if(str == "+" || str == "-" || str == "*" || str == "/")
        {
            int b = st1.top();
            st1.pop();
            int a = st1.top();
            st1.pop();
            st1.push(mp1[str](a,b)); 
        }

        else
        {
            st1.push(stoi(str));
        }
    }

    return st1.top();
    }
};

ak截图:

在这里插入图片描述

  • 这题如果不使用function,就要用if或者switch表达式代码看着很冗余,不够优雅,但是使用map容器建立op-method的映射关系,就看起来十分优雅。(这题的具体算法思路,参考这篇博客)

绑定

绑定是C++11中新出来的语法,它是一个函数模板,下面是它的函数模板参数及其函数形参。

在这里插入图片描述

bind的形参使用万能引用,既可传左值也可传右值,它的返回值类型比较复杂,没有给出,但是我们可以把它的返回值认为是function类型。它重载了两个版本,我们只介绍第一个版本,第二个和第一个区别不大,就是多了一个返回值类型让我们传。

  • Fn是可调用对象的类型,args是可调用对象的参数包。

在这里插入图片描述

下面我们将介绍bind的使用,它有如下两种用途:

绑定修改可调用对象的参数个数

下面是代码:

#include<iostream>
#include<functional>

using namespace std;

class Sub//仿函数对象
{
public:
	int operator()(int x, int y)
	{
		return x - y;
	}

	int Mul(int x, int y)//仿函数中的普通函数方法
	{
		return x * y;
	}
};
int Add(int x, int y)//函数
{
	return x + y;
}
int main()
{
	//绑定普通函数
	int num1 = 3;
	function<int(int)> func1 = bind(Add,num1,placeholders::_1);//placeholders::_1代表我们要传的第一个参数对应Add中的第二个参数
	cout << typeid(func1).name() << endl;
	cout << func1(3) << endl;

	//绑定仿函数对象
	int num2 = 4;
	auto func2 = bind(Sub(), num2, placeholders::_1);
	cout << typeid(func2).name() << endl;
	cout << func2(3) << endl;

	//绑定仿函数对象中的普通方法
	int num3 = 4;
	auto func3 = bind(&Sub::Mul,Sub(), placeholders::_1, placeholders::_2);
	cout << typeid(func3).name() << endl;
	cout << func3(num3, 4) << endl;

	//绑定lambda表达式
	auto func4 = bind([](int x, int y)->int {return x + 2 * y; }, 5, placeholders::_1);
	cout << typeid(func4).name() << endl;
	cout << func4(5) << endl;
	return 0;
}

运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,使用bind我们确实可以绑死可调用对象的一些参数,特别当我们需要在其它文件中调用类中的方法时,把第一个类对象给绑死,可以让我们使用起来像普通函数一样很方便。

另外从运行结果中还可以看出,虽然auto关键字推导出的bind返回值的类型很复杂,但是我们的第一个普通函数使用function类模板作为其类型也是可以的,前提是其返回值类型和参数类型和原调用对象以及bind中相应的处理保持一致。(特指使用bind绑死了一个参数,或者调换了某些参数的调用顺序)

绑定修改可调用对象的参数的调用顺序

下面是代码:

#include<iostream>
#include<algorithm>
#include<functional>
#include<vector>
using namespace std;

int main()
{
	//我们可以使用bind调整类中方法的调用顺序

	auto func1 = bind(sort<vector<int>::iterator,less<int>>, placeholders::_2, placeholders::_1,placeholders::_3);
	vector<int> v1;

	for (int i = 0; i < 10; ++i)
		v1.emplace_back(10 - i);
	
	for (auto& i : v1)
		cout << i << " ";

	cout << endl;
	func1(v1.end(), v1.begin(),less<int>());

	for (auto& i : v1)
		cout << i << " ";

	cout << endl;
	return 0;
}
  • 上述代码中我们修改了algorithm库中sort函数模板的前两个参数的调用顺序。对于函数模板来说,绑定可调用对象必须传参数编译器才会帮助我们实例化出具体的函数。下面是sort函数模板的参数及其模板类型,它重载了两个函数模板:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们不需要传比较器(用于比较的仿函数对象),就可以使用第一个函数模板,不显式的传它的比较器类型;否则必须传。

运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于bind的底层实现我们不用过多关注,我们可以发现它和function的传参(特别是在传类中的函数方法时)很相似。它的返回值类型也肯定和function类型有一定联系,如果你感兴趣可以自行阅读源码,本篇博客只讲使用。

  • 本人知识、能力有限,若有错漏,烦请指正,非常非常感谢!!!
  • 转发或者引用需标明来源。

http://www.kler.cn/news/363395.html

相关文章:

  • 鼠标移入盒子,盒子跟随鼠标移动
  • 你不常用的 FileReader 能干什么?
  • WPF中的Style如何使用
  • visual studio设置修改文件字符集方法
  • 简单的 curl HTTP的POSTGET请求以及ip port连通性测试
  • Python 代码实现对《红楼梦》文本的词频统计和数据可视化
  • 企业建立质量管理系统的目的是什么?
  • RHCE的练习(5)
  • vue3中使用element-plus的组件,编辑器检查警告爆红找不到名称相关的element组件
  • 软考中级网络工程师,快背,都是精华知识点!
  • 基于springboot美食商城推荐系统
  • flink使用hikariCP数据库连接池,导致连接泄露
  • 【JVM】—G1 GC日志详解
  • Spring Boot:植物健康监测的智能管家
  • 【JVM】—G1中的Young GC、Mixed GC、Full GC详解
  • [Linux] CentOS7替换yum源为阿里云并安装gcc详细过程(附下载链接)
  • 【APIPost】学习与实践,如何使用 APIPost 测试 Java 后端项目
  • 5分钟搞懂 Python 中的 ‘==‘ 和 ‘is‘ ,看这篇就够了
  • Stack和Queue(3)
  • Single Engine + All Data :云器科技怎么基于“增量计算”的一体化湖仓平台,构建新一代流批一体数据平台,
  • 重构复杂简单变量之用类替换类型码
  • DevExpress WPF中文教程:Data Grid的视图概述及主要功能一览
  • Java之动态代理
  • MoeCTF 2024 ---Misc方向WP
  • 【WPF】中Dispatcher的DispatcherPriority参数使用
  • 滚雪球学Redis[8.2讲]:Redis的未来发展趋势:从云服务到AI与物联网的前沿探索