C++之 bind 绑定器深入学习:从入门到精通!

简介

本文详细阐述了 C++ 中关于 bind 绑定器技术的基本概念和常用技巧。

引入动机

在标准算法库中,有一个算法叫 remove_if,其基本使用如下:

#include <iostream> 
#include <string>
#include <algorithm>
#include <vector>

using namespace std;

bool func(int value) {
	return value > 4;
}

void test() {
	vector<int> number = { 1,4,7,9,5,6,4,3,2,7 };
	//remove_if不会将满足条件的数据删除,但是会返回待删除元素的首地址
	//然后配合 erase 使用
	auto it = remove_if(number.begin(), number.end(), func);
	cout << "remove_if后erase前:" << endl;
	for (auto i = number.begin(); i != number.end(); ++i) {
		cout << *i << " ";
	}
	cout << endl;
	
	cout << "erase后:" << endl;
	number.erase(it, number.end());
	for (auto i = number.begin(); i != number.end(); ++i) {
		cout << *i << " ";
	}
	cout << endl;
}

int main() {
	test();
	return 0;
}
	

运行结果如下:

在这里插入图片描述

对于 remove_if 来说,其第三个参数需要的是一个一元谓词,也就是上面的 func 函数。

那么我们能不能传一个二元谓词呢?

如果我们传一个二元谓词,然后将其中一个参数给固定,那么此时二元谓词不就变成了一元谓词了吗?

因此我们可以像下面这么写:

bool func2(int num1, int num2=4){
	return num1 > num2;
}

我们想要实现的事情是将 number.begin() 到 number.end() 之间的数字中大于 4 的给删掉,那么意思就是将这个 begin() 和 end() 之间的数传递给函数 func2 的 num1 参数,然后 num2 参数固定下来为 4 即可。

而此时要表明这个 func2 函数要有大于符号的意思,那么就可以使用函数对象类模板 greater :

template<typename T>  
class greater {  
public:  
    bool operator()(const T& lhs, const T& rhs) const {  
        return lhs > rhs;  
    }  
};  

上面的 Greater 类定义了一个重载的 () 操作符,它接受两个类型为 T 的参数,并返回一个布尔值,表示第一个参数是否大于第二个参数。然后,你可以像使用 std::greater 一样使用你的 Greater 类模板来进行排序或其他需要比较函数对象的操作。

例如:

// 使用示例  
#include <vector>  
#include <algorithm>  
#include <iostream>  
  
int main() {  
    std::vector<int> vec = {1, 2, 3, 4, 5};  
      
    // 使用自定义的 Greater 类模板进行降序排序  
    std::sort(vec.begin(), vec.end(), Greater<int>());  
      
    // 输出排序后的向量  
    for (int num : vec) {  
        std::cout << num << " ";  
    }  
    std::cout << std::endl;  
      
    return 0;  
}

补充一下函数对象类模板的概念:

在这里插入图片描述

那么此时就有一个问题,我们怎么让这个 4 绑定到函数的第二个参数上呢?怎么固定下来?

此时就引出了我们的 bind 绑定器的概念。

bind 1st 和 bind 2nd

Cpp_Reference 中的介绍如下:

在这里插入图片描述

可以看到不管是 bind 1st 还是 bind 2nd,它们的第一个参数都是 f,表示函数;而第二个参数都是 x,这个 x 的话就表示一个值。

这个值和 f 之间的关系是:如果是 bind 1st,那么 x 表示绑定到 f 函数的参数列表中的第一个值;如果是 bind 2nd,那么 x 表示绑定到 f 函数的参数列表中的第二个值。

那么通过 bind 技术,我们就可以完成绑定操作了,按照我们之前的思路,代码只要如下改即可:

void test() {
	vector<int> number = { 1,4,7,9,5,6,4,3,2,7 };
	//使用bind2nd函数,将4固定到greater比较器的第二个参数实现删除number中所有大于4的值
	auto it = remove_if(number.begin(), number.end(), bind2nd(greater<int>(), 4));
	cout << "remove_if后erase前:" << endl;
	for (auto i = number.begin(); i != number.end(); ++i) {
		cout << *i << " ";
	}
	cout << endl;
	cout << "erase后:" << endl;
	number.erase(it, number.end());
	for (auto i = number.begin(); i != number.end(); ++i) {
		cout << *i << " ";
	}
	cout << endl;
}

运行结果相同:

在这里插入图片描述

小结一下就是:remove_if 的第三个参数需要一个一元谓词,但是传进来的是二元谓词的时候,需要固定其中一个参数使得二元谓词变成一元谓词,以符合 remove_if 的传参规则,而 bind 绑定器技术可以帮助我们实现这个事情。

bind

上面的 bind1st 和 bind2nd 的用法还比较机械,且只能绑定一个二元的函数,即函数参数为 2 的函数。

对于 n 元函数这俩就傻眼了,因此我们还有终极大招:bind !

这才是本文的重点,最终的绑定器技术:

在这里插入图片描述

另外对比上刚刚的 bind1st 和 bind2nd 的 cpp_reference 中的截图不难发现,只有 bind 是从 C++11 标准中提供并且到现在依然还在使用的,而 bind1st 和 bind2nd 已经过时,甚至在 C++17 中已经被抛弃了。

可以看到其第一个参数依然是一个 f 函数,但是第二个参数则是可变参数了,意味着数量可能有多个。

因此此时的 bind 就可以绑定 n 元函数了。

另外还应该注意到的一点是 bind 函数的返回类型是没有明确的 unspecified ,因此如果我们要接收该函数的返回值,应该使用 auto 。

bind 作用于普通函数

接下来我们来看一下 bind 的基本使用:

#include <iostream> 
#include <functional>

using namespace std;

int add(int x,int y,int z) {
	cout << "int add(int, int, int)" << endl;
	return x + y + z;
}

void test() {
	//函数类型靠函数的返回类型和参数列表进行确定
	//因此add的函数类型为:int (int, int, int)
	//同时因为add函数经过bind的改造,其三个参数都分别被固定为了1,2,3
	//因此 f 的函数类型其实就为:int ()
	//即返回类型为 int,但是参数列表为空
	auto f = bind(add, 1, 2, 3);
	//调用函数 f
	cout << "f() = " << f() << endl;
}

int main() {
	test();
	return 0;
}
	

在这里插入图片描述

上面这是 bind 作用于普通的函数使用。

bind 作用于类的成员函数

#include <iostream> 
#include <functional>

using namespace std;

class Example {
public:
	int add(int x, int y) {
		cout << "int Example::add(int, int)" << endl;
		return x + y;
	}
};

void test() {
	/*
	* 对于类成员函数使用 bind 时需要注意下面几点:
	* 1、因为普通函数的函数名就是入口地址,因此加不加&都可以
	*	 但是对于类成员函数则必须要加取地址符号&
	* 2、对于类成员函数来说,因为我们引用的不是静态成员函数而是普通成员函数,
	*	 因此其第一个参数是隐含的this指针,在绑定时我们需要将这个this指针
	*	 作为第一个参数一并传入
	*/
	//Example中的add函数类型为:int (this, int, int)
	//那么 f 经过 bind 过后函数类型为:int ()
	Example ex;
	auto f = bind(&Example::add, &ex, 20, 39);
	cout << "f() = " << f() << endl;
}

int main() {
	test();
	return 0;
}

在这里插入图片描述

而这种用法,很像函数指针!

bind 与函数指针

直接看代码:

int func1(int x) {
	cout << "int fun1(int) " << endl;
	return x;
}

int func2(int y) {
	cout << "int fun2(int) " << endl;
	return y;
}

void test2() {
	//函数类型为 int (int),int 表示返回值,(int) 表示接收一个参数int
	//此时我们需要一个指针来指向这种类型的函数,因此用 *pFunc
	//此时的 *pFunc 就是函数指针
	//顺便提一下指针函数指的是返回值类型是指针类型的函数
	//比如 int* pf(int=0){}
	int (*pFunc)(int);
	//让函数指针 pFunc 指向 func1 函数
	//func1 是普通函数,因此函数名就是入口地址,可以不写&
	pFunc = &func1;
	
	//现在我们想把 pFunc 做成返回值类型是int,参数也是int 的这样一种类型怎么办呢?
	//使用重定义即可,这时候 pFunc 就是一种类型了
	//实际上现在的语法像下面这样写会更好懂一点,
	//其实是应该像这样的:typedef int(int) pFunc1;
	//这就表示 pFunc1 是一种形似 int(int) 的函数类型
	typedef int (*pFunc1)(int);
	//因为 pFunc1 是一种类型,所以我们可以用它创建对象
	pFunc1 f = &func1;//注册回调函数(这是函数指针一个经典的用法)
	cout << "f(10)=" << f(10) << endl;//回调函数的调用(这是函数指针一个经典的用法)

	f = func2;
	cout << "f(20)=" << f(20) << endl;
}

int main() {
	test2();
	return 0;
}

在这里插入图片描述

对比之前所使用的 bind 技术,可以发现二者有诸多相同之处,这里就不再赘述。

bind 中的 placeholders 与 cref()

对于函数指针,当我们将它声明完的那一刻,它就已经固定了类型无法更改,不够灵活。

比如之前的代码中:

typedef int (*pFunc1)(int);
pFunc1 f = &func1;
cout << "f(10)=" << f(10) << endl;

我们不再让 f 指向 func1 ,而是指向下面的函数的话:

int add(int x, int y, int z){
	return x+y+z;
}

肯定会报错,因为类型都不匹配。

但是我们使用 bind 则可以解决其灵活性不足的问题:

#include <iostream> 
#include <functional>

using namespace std;

int add(int x, int y, int z) {
	return x + y + z;
}

int func1(int x) {
	cout << "int fun1(int) " << endl;
	return x;
}

int func2(int y) {
	cout << "int fun2(int) " << endl;
	return y;
}

void test2() {
	typedef int (*pFunc)(int);
	pFunc f = &func1;
	cout << "f(10)=" << f(10) << endl;

	f = func2;
	cout << "f(20)=" << f(20) << endl;

	//f = add; ;类型不匹配,报错
	//使用 bind 来解决
	//add的函数类型为:int(int,int,int)
	//bind过后f3的函数类型为:int(int,int)
	/*
	* 对于add的第一个参数,我们使用了 100 来绑定
	* 对于第二个和第三个参数我们则是采用了placeholders占位符先占位
	*/
	auto f3 = bind(&add, 100, placeholders::_1, placeholders::_2);
	cout << "f3() = " << f3(20,30) << endl;

}

int main() {
	test2();
	return 0;
}
	

运行结果如下:

在这里插入图片描述

可以发现,bind 比函数指针要灵活的多,函数指针只能去指向某种固定类型的函数类型,但是 bind 可以去绑定一个函数,这个函数不管有多少参数我可以绑定其中一个,绑定其中两个…等随意数量的绑定。

另外,假如我们传入了额外的参数会发现也并不会报错:

cout << "f3() = " << f3(20,30,40,50,832,28,48) << endl;

为什么不会报错?众多参数之中又是如何进行选取的?

这都是由于我们在 bind 中所采用的 placeholders 占位符所决定的:

auto f3 = bind(&add, 100, placeholders::_1, placeholders::_2);

placeholders 就是占位符,而后面跟的下划线数字最多可以传到 _29 。

占位符本身代表的是形参的位置,而占位符后的下划线数字则代表的是实参的位置。

以上面的代码为例的话,因为调用 f3 函数时传入的顺序前两个是 20 和 30,而在 bind 时在第二和第三个实参位置采取的是 placeholders::_1 和 placeholders::_2 ,因此最后 f3 采用的就是前两个值 20 和 30,多余的就看不到了 。

这就是占位符和占位符后面数字所代表的含义。

可以再来一个例子感受一下:

void func3(int x1, int x2, int x3, const int& x4, int x5) {
	cout << "x1 = " << x1 << endl
		<< "x2 = " << x2 << endl
		<< "x3 = " << x3 << endl
		<< "x4 = " << x4 << endl
		<< "x5 = " << x5 << endl;
}

void test3() {
	//占位符本身代表的是形参的位置
	//占位符中的数字代表的是实参的位置
	//另外 bind 中默认使用的是值传递
	int number = 100;
	auto f = bind(&func3, 1, placeholders::_1, placeholders::_6,
		number, number);
	number = 700;
	f(20, 40, 300, 200, 600, 800);
}

int main() {
	test3();
	return 0;
}

在这里插入图片描述

但是上面的代码中有一个问题是我们的第四个参数是使用 const 引用传入的,按道理我们的 number 值发生该变那么打印出来的 x4 的值也应该发生改变(从 100 变成 700),但上面没有发生这样的变化,这是因为 bind 在绑定值的时候默认使用的是值传递。

为了使得引用传递生效,需要使用 cref() 函数:

void test3() {
	//占位符本身代表的是形参的位置
	//占位符中的数字代表的是实参的位置
	//另外 bind 中默认使用的是值传递
	int number = 100;
	//可以使用 cref() 函数将传入参数按 const 引用类型传入
	auto f = bind(&func3, 1, placeholders::_1, placeholders::_6,
		cref(number), number);
	number = 700;
	f(20, 40, 300, 200, 600, 800);
}

运行结果如下:

在这里插入图片描述

此时就发生了改变,这个 cref() 函数被称为引用包装器。

如果只是普通的引用的话,那么可以选择使用 ref() ,使用方式同上,不再赘述。

function

在前面的学习中,我们现在已经知道了 bind 的使用方法。

auto f = bind(&func3, 1, placeholders::_1, placeholders::_6,
		cref(number), number);

但是有一个问题,之前我们都是自己从逻辑上推理出的 bind 函数的返回类型,但是我们知道了函数的返回类型我们能怎么进行操作呢?意思是怎么用一种特殊的形式来写出 auto 所代表的类型?

这就需要用到 function:

在这里插入图片描述

乍一看,似乎不太好明白这个 function 的声明是在干嘛、是如何使用的,但其实我们可以类比 vector 的声明形式一起看,因为它俩是差不多的:

template<class>
class function; /* undefined */
// 下面是 function 的声明
template< class R, class... Args>
class function<R(Args...)>

//下面是 vector 的声明
template<class T, class Allocator = std::allocator<T>>
class vector;

这样看起来是不是挺像的?

那么来看一下我们的 vector 是怎么使用的:

vector<int> number;

那我们就可以依葫芦画瓢,写出相同的 function 的使用方法:

function<类型> func;

那么现在关键就是看这个类型究竟如何定义的,在 function 的声明中可以看到其定义如下:

template< class R, class... Args>
class function<R(Args...)>

R(Args…) 都是来自模板参数提供,而 R 其实就是类型,Args… 实际上就是参数,因此我们就可以如下使用:

function<int(int,int,int)> func;

是不是很眼熟?int(int,int,int) 这不就是我们的函数类型吗?

因此 function 就相当于一个容器,专门用来存放某一种函数类型的数据,即可以存放一个一个相同函数类型的函数。

通过 function 接收 bind 函数返回值类型

通过 function,我们就可以写出之前使用 auto 来隐藏的具体的 bind 函数的返回类型是什么了:

#include <iostream> 
#include <functional>

using namespace std;

int add(int x, int y, int z) {
	cout << "int add(int, int, int)" << endl;
	return x + y + z;
}

void test() {
	//因为add的三个参数都被绑定了,因此f的函数类型为int()
	function<int()> f = bind(add, 1, 2, 3);
	cout << "f() = " << f() << endl;

	//像下面这样写会报错,因为没有绑定参数的位置应该提供占位符而不能直接空着
	//function<int(int,int)> f2 = bind(add, 1); 报错
	function<int(int, int)> f2 = bind(add, 1, placeholders::_1, placeholders::_2);
	cout << "f2() = " << f2(23,40) << endl;
}

int main() {
	test();
	return 0;
}

bind 作用到类数据成员上

之前都提过,bind 可以绑定到普通函数(如 add)上面来,可以绑定到类成员函数(如 Example::add)上面来,甚至我们也可以让 bind 绑定到一个类的数据成员上面来,来试一下:

class Example {
public:
	int add(int x, int y) {
		cout << "int Example::add(int, int)" << endl;
		return x + y;
	}
	//data 是 Example 类中的一个数据成员
	int data = 200;
};

我们将思维转换一下,data 其实可以看作是一个函数,看成返回类型是 int,参数是一个无参的这样一个函数,因此同样可以使用 bind 绑定:

#include <iostream> 
#include <functional>

using namespace std;

class Example {
public:
	int add(int x, int y) {
		cout << "int Example::add(int, int)" << endl;
		return x + y;
	}
	//data 是 Example 类中的一个数据成员
	int data = 200;
};

void test() {
	
	function<int()> f = bind(add, 1, 2, 3);
	cout << "f() = " << f() << endl;

	Example ex;
	f = bind(&Example::data, &ex);
	cout << "类数据成员:f() = " << f() << endl;
	
}

int main() {
	test();
	return 0;
}

在这里插入图片描述

因此可以得出结论,bind 可以绑定到 n 元函数,该函数可以是自由函数、全局函数(非成员函数),也可以绑定到成员函数,甚至可以绑定到数据成员上。

同时我们也清楚了 bind 是可以改变函数的形态的(改变函数的类型),函数的类型包括函数的返回类型,以及函数的参数列表(即函数参数的个数,函数参数的顺序,函数参数的类型)。

而 function 呢就是用来接收函数的类型,function 是函数的容器。

最后,通过 bind 和 function 组合使用可以实现多态技术。

bind + function 实现多态技术

先看一段多态技术的示例代码:

#include <iostream> 
#include <math.h>
#include <functional>

using namespace std;

class Figure {
public:
	virtual void display() = 0;
	virtual double area() = 0;
};

class Rectangle: public Figure {
public:
	Rectangle(double length = 0, double width = 0) :_length(length), _width(width) {

	}
	void display() override {
		cout << "Rectangel";
	}
	double area() override {
		return _length * _width;
	}
private:
	double _length;
	double _width;
};

class Circle : public Figure {
public:
	Circle(double radius=0) :_radius(radius) {

	}
	void display() override {
		cout << "Circle";
	}
	double area() override {
		return 3.14 * _radius * _radius;
	}
private:
	double _radius;
};

class Triangle : public Figure {
public:
	Triangle(double a = 0, double b = 0, double c = 0) 
		:_a(a),
		_b(b),
		_c(c) {

	}
	void display() override {
		cout << "Triangel";
	}
	double area() override {
		double tmp = (_a + _b + _c) / 2;
		return sqrt(tmp * (tmp - _a) * (tmp - _b) * (tmp - _c));
	}
private:
	double _a;
	double _b;
	double _c;
};

void func(Figure* pFig) {
	pFig->display();
	cout << "的面积为 : " << pFig->area() << endl;
}

int main() {
	Rectangle rectangle(10, 20);
	Circle circle(10);
	Triangle triangle(3, 4, 5);

	func(&rectangle);
	func(&circle);
	func(&triangle);
	return 0;
}

运行结果如下:

在这里插入图片描述

那么接下来我们就使用 bind + function 的方式来实现这样的多态技术。

因为与继承无关了,因此我们需要删除掉上面示例代码中关于继承的内容:

#include <iostream> 
#include <math.h>
#include <functional>

using namespace std;

class Figure {
public:
	virtual void display() = 0;
	virtual double area() = 0;
};

class Rectangle{
public:
	Rectangle(double length = 0, double width = 0) :_length(length), _width(width) {

	}
	void display()  {
		cout << "Rectangel";
	}
	double area()  {
		return _length * _width;
	}
private:
	double _length;
	double _width;
};

class Circle{
public:
	Circle(double radius=0) :_radius(radius) {

	}
	void display()  {
		cout << "Circle";
	}
	double area()  {
		return 3.14 * _radius * _radius;
	}
private:
	double _radius;
};

class Triangle{
public:
	Triangle(double a = 0, double b = 0, double c = 0) 
		:_a(a),
		_b(b),
		_c(c) {

	}
	void display()  {
		cout << "Triangel";
	}
	double area()  {
		double tmp = (_a + _b + _c) / 2;
		return sqrt(tmp * (tmp - _a) * (tmp - _b) * (tmp - _c));
	}
private:
	double _a;
	double _b;
	double _c;
};

void func(Figure* pFig) {
	pFig->display();
	cout << "的面积为 : " << pFig->area() << endl;
}

int main() {
	Rectangle rectangle(10, 20);
	Circle circle(10);
	Triangle triangle(3, 4, 5);

	func(&rectangle);
	func(&circle);
	func(&triangle);
	return 0;
}

在 Figure 类中,其有两个函数 display 和 area,这个类本身作为接口存在由其它的类来实现该接口。

这两个函数的参数都为void,只有返回值不同,我们先将它们取个别名:

#include <iostream> 
#include <math.h>
#include <functional>

using namespace std;

class Figure {
public:
	//给这两个函数类型起别名
	using DisplayCallback = function<void()>;
	using AreaCallback = function<double()>;

	//根据起的别名创建对象
	DisplayCallback _displayCallback;
	AreaCallback _areaCallback;

	//注册回调函数,采用右值接收的原因是为了避免拷贝
	//相当于:void setDisplayCallback(function<void()>&& cb){} 
	void setDisplayCallback(DisplayCallback&& cb) {
		//这里可以用move也可以不用move,但是使用move可以避免拷贝操作
		//因为这里是两个对象间的赋值运算,势必可能会用到赋值拷贝
		//因此使用move可以尽可能避免拷贝操作,增加效率
		_displayCallback = move(cb);
	}

	//注册回调函数
	void setAreaCallback(AreaCallback&& cb) {
		_areaCallback = cb;
	}

	//执行回调函数
	void handlerDisplayCallback() const {
		if (_displayCallback) {
			_displayCallback();
		}
	}

	double handlerAreaCallback() const {
		if (_areaCallback) {
			//因为AreaCallback的函数类型返回值是double
			return _areaCallback();
		}
		else {
			return 0;
		}
	}

	
};

class Rectangle{
public:
	Rectangle(double length = 0, double width = 0) :_length(length), _width(width) {

	}
	void display()  {
		cout << "Rectangel";
	}
	double area()  {
		return _length * _width;
	}
private:
	double _length;
	double _width;
};

class Circle{
public:
	Circle(double radius=0) :_radius(radius) {

	}
	void show()  {
		cout << "Circle";
	}
	double showArea()  {
		return 3.14 * _radius * _radius;
	}
private:
	double _radius;
};

class Triangle{
public:
	Triangle(double a = 0, double b = 0, double c = 0) 
		:_a(a),
		_b(b),
		_c(c) {

	}
	void print(int x)  {
		cout << "Triangel";
	}
	double printArea()  {
		double tmp = (_a + _b + _c) / 2;
		return sqrt(tmp * (tmp - _a) * (tmp - _b) * (tmp - _c));
	}
private:
	double _a;
	double _b;
	double _c;
};

void func(const Figure& pFig) {
	//执行回调函数
	pFig.handlerDisplayCallback();
	cout << "的面积为 : " << pFig.handlerAreaCallback() << endl;
}

int main() {
	Rectangle rectangle(10, 20);
	Circle circle(10);
	Triangle triangle(3, 4, 5);

	Figure fig;
	//回调函数的注册
	//此时bind函数返回的类型正好就是setDisplayCallback函数所需要的function类型
	fig.setDisplayCallback(bind(&Rectangle::display,&rectangle));
	fig.setAreaCallback(bind(&Rectangle::area, &rectangle));
	func(fig);

	fig.setDisplayCallback(bind(&Circle::show, &circle));
	fig.setAreaCallback(bind(&Circle ::showArea, &circle));
	func(fig);

	//传入一个参数也一样没有问题,只要符合函数类型即可
	fig.setDisplayCallback(bind(&Triangle::print, &triangle, 3));
	fig.setAreaCallback(bind(&Triangle::printArea, &triangle));
	func(fig);

	return 0;
}

运行结果如下:

在这里插入图片描述

可以看见一样可以实现多态技术。

之前使用继承和虚函数的方法被称为面向对象的方法,而 bind+function 的这一种方法叫基于对象的方法(没有使用到继承那就是基于对象的方法)。

补充成员函数绑定器:mem_fn() 函数

最后再补充一个 mem_fn() 函数的知识点。

直接上代码:

#include <iostream> 
#include <vector>
#include <algorithm>
#include <functional>

using namespace std;

class Number {
public:
	Number(size_t data = 0):_data(data) {

	}

	void print() const {
		cout << _data << " ";
	}

	//判断是否是一个偶数
	bool isEven() const {
		return (0 == _data % 2);
	}

	bool isPrime() const {
		if (1 == _data) {
			return false;
		}
		//是否是质数/素数
		for (size_t idx = 2; idx != _data / 2; ++idx) {
			if (_data % 2 == 0) {
				return false;
			}
		}
		return true;
	}

private:
	size_t _data;
};

int main() {
	vector<Number> vec;
	
	for (size_t idx = 0; idx != 30; ++idx) {
		vec.push_back(Number(idx));
	}
	//此时的调用 print() 是会报错的,因为 print 不是一个普通函数
	//而是一个成员函数
	//for_each(vec.begin(), vec.end(), print); 报错
	//for_each(vec.begin(), vec.end(), &Number::print); 依然报错
	
	//对于这种情况,我们必须使用 mem_fn() 这个函数才能解决
	for_each(vec.begin(), vec.end(), mem_fn(&Number::print));
	return 0;
}

运行结果如下:

在这里插入图片描述

从上面代码我们知道了 mem_fn 的使用方法和使用场景,那么再来看一下 Cpp_Reference 上的对于 mem_fn 的介绍:

在这里插入图片描述

明显 mem_fn 是一个函数模板,但它的参数非常的有意思:

mem_fn(M T::* pm)

感觉分开都看得懂,但是合在一起就不太懂了。

这是因为我们不懂成员函数指针。

成员函数指针

先来看普通函数指针:

int (*pFunc)(int, int);

那么什么是成员函数指针?成员函数指针顾名思义所指向的是属于一个类里面的成员函数。

那为什么会有这个成员函数指针的概念呢?

因为大家会发现,只要函数是属于类里面的成员函数的话,那这个时候成员函数则肯定会有一个 this 指针作为该成员函数的第一个也是隐含的参数。因此针对于类的成员函数,有一个特定的概念叫成员函数指针:

class Test{
public:
	int add(int x, int y){
	
	}
};

//对于非静态的成员函数,都会在第一个参数的位置隐藏一个this指针
//因此会有一个成员函数指针如下专门用来解决 this 指针的问题
int (Test::*pFunc)(int, int);
//此时使用成员函数指针去指向成员函数 add 就没有问题了
pFunc = &Test::add;

仔细看成员函数指针的形式,就会发现和我们的 mem_fn 的参数形式差不多,这也就是为什么我们的代码能有效运行的原因:

for_each(vec.begin(), vec.end(), mem_fn(&Number::print));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在地球迷路的怪兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值