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

news2024/9/28 11:14:07

简介

本文详细阐述了 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));

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

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

相关文章

FANUC发那科模块 A03B-0823-C003 I/0 EXT

IO模块接线 在FANUC系统中IO模块的种类比较多&#xff0c;每种IO模块的使用场合也不相同&#xff0c;每种IO模块的接线脚位也有很大区别&#xff0c;对于电气设计人员来说&#xff0c;清楚知道常用IO模块的接线脚位&#xff0c;才能更好的规划地址、设计图纸&#xff0c;对于设…

MySQL多表

表关系 1.一对多 应用场景 班级和学生 部门和员工 建表原则 设置&#xff08;ForeginKey&#xff09;外键连接 一个表的外键即为另外一张表的主键,以此简历两张表的关系 因此需要再学生表中新增一列&#xff0c;命名为 班级表_id&#xff0c;即班级表的主键&#xff0c;又叫…

【力扣】572.另一棵树的子树

题目描述 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看…

电脑屏幕录制工具分享5款,附上详细电脑录屏教程(2024全新)

日月更迭&#xff0c;转眼间已经来到了2024年的立秋&#xff0c;在这个数字技术快速发展的时代&#xff0c;电脑录屏技术已经成为了一项不可或缺的技能&#xff0c;无论是用于工作汇报、在线教学、游戏直播还是个人娱乐。那么录屏软件哪个好用呢&#xff1f;接下来&#xff0c;…

QT按钮组

目录 按钮组 Push Button&#xff08;按钮&#xff09; Tool Button&#xff08;图片文字&#xff09; Radio Button(单选&#xff09; Check Button(多选) Command Link Button Dialog Button Box(对话按钮&#xff09; 按钮组 Push Button&#xff08;按钮&#xff09…

手机游戏录屏软件哪个好,3款软件搞定游戏录屏

在智能手机普及的今天&#xff0c;越来越多的人喜欢在手机上玩游戏&#xff0c;并希望能够录制游戏过程或者分享游戏技巧。然而&#xff0c;面对市面上众多的手机游戏录屏软件&#xff0c;很多人可能会陷入选择困难。究竟手机游戏录屏软件哪个好&#xff1f;在这篇文章中&#…

数据跨境传输的安全合规风险如何规避?获取免费解决方案白皮书

在全球化的背景下&#xff0c;企业进行有 效的资源整合&#xff0c;学习海外市场的先进技术和管理经验&#xff0c;寻找新的增长点&#xff0c;实现业务的多元化和 可持续发展&#xff0c;不仅有利于开辟新市场&#xff0c;更有助于巩固和增强企业在全球中的地位。在这种前景 下…

如何把项目上传到Gitee(超详细保姆级教程)

目录预览 一、远程仓库1、新建远程仓库1.1 克隆/下载信息介绍 2、新建分支3、配置私人令牌 二、本地仓库1、初始化本地仓库2、创建分支&#xff0c;并切换到该分支3、设置用户名、邮箱3.1 全局3.2 局部 4、设置Remote地址4.1 远程仓库有文件4.2 远程仓库没有文件 5、拉取最新代…

全面掌握Xilinx FPGA开发技术与实战技巧

FPGA以其灵活性、可定制性和并行处理能力&#xff0c;为工程师提供了实现创新解决方案的强大工具。对于初学者来说&#xff0c;学习FPGA开发需要掌握一些基础知识和技能。 学习FPGA必备的基础知识点&#xff1a; 数字逻辑基础&#xff1a;理解基本的数字逻辑概念&#xff0c;…

基于danceTrack相关论文代码列表

文章目录 数据集下载2023Observation-Centric SORT: Rethinking SORT for Robust Multi-Object Tracking 数据集下载 https://github.com/DanceTrack/DanceTrack 2023 Observation-Centric SORT: Rethinking SORT for Robust Multi-Object Tracking code: https://github.c…

微型导轨:光学仪器精准定位的支撑者

微型导轨是指宽度在25mm以下的导轨系统&#xff0c;通常由导轨和滑块组成&#xff0c;具有体积小、重量轻、精度高、噪音低、寿命长等特点。主要用于支撑和定位光学元件&#xff0c;如镜子、透镜、滤光片等。微型导轨通过提供高精度的运动控制&#xff0c;‌有利于提高设备的性…

重磅发布 |《一本书讲透数据资产入表》在全球数据资产大会上发布

2024年8月2日&#xff0c;全球数据资产大会在厦门举行&#xff0c;数据资产管理标杆厂商亿信华辰正式发布全新力作《一本书讲透数据资产入表》&#xff0c;荣获“数据资产十大先锋机构”&#xff0c;并发表主题演讲&#xff0c;展现其在数据资产管理领域的领军风采与创新实力。…

macOS Java多版本管理工具

macOS Java多版本管理工具 可以使用 sdkman&#xff0c;也可以使用jenv 能用 sdkman 就建议使用 sdkman &#xff0c;用不了就使用 jenv # sdkman的安装及使用 蚁景网安学院-一个开放的网络安全交流学习论坛 # jenv 的安装及使用 # 安装JDK8 下载 JDK8 JDK8下载页面&…

Ubuntu环境安装MySQL

Ubuntu环境安装MySQL 1. 访问下载界面并下载发布包2. 安装发布包3. 安装MySQL 1. 访问下载界面并下载发布包 下载地址 也可直接去mysql.com官网下载 这里如果要下载其他版本的或可以去http://repo.mysql.com/这个网页查询相关的版本。 2. 安装发布包 使用切换到root用户…

美元兑人民币汇率的变化,对A股直接影响是什么

美元兑人民币汇率的变化对A股的直接影响是复杂且多面的&#xff0c;主要体现在以下几个方面&#xff1a; 一、市场情绪与投资者信心 汇率波动引发市场担忧&#xff1a;当美元兑人民币汇率大幅波动时&#xff0c;尤其是人民币贬值&#xff0c;可能会引发市场担忧&#xff0c;影…

数据复盘“黑色星期一”:加密市场震荡,代币表现如何?

8月5日的“黑色星期一”成为了全球金融市场的动荡日&#xff0c;这一波及到加密市场的剧烈震荡导致了大量清算事件和代币的暴跌。本文将通过数据复盘&#xff0c;分析这一事件中加密货币的表现&#xff0c;并探讨未来市场的可能走向。 一、暴跌中的惨痛数据 在“黑色星期一”事…

Linux初次体验

Linux系统也是的命令字符也是多的离谱&#xff0c;本来不想写的就顺便写写吧 首先 ctrlaltT 打开终端 这里我就创建了一个文件&#xff0c;test&#xff0c;使用 vi 文件名.c 默认模式是命令行模式 无法写代码的 输入 i 后进入输入模式&#xff0c;开始写代码 退出输入模式…

032_java.util.concurrent.ConcurrentHashMap

继承体系 HashMap是我们用得非常频繁的一个集合&#xff0c;但是由于它是非线程安全的&#xff0c;在多线程环境下&#xff0c;put操作是有可能产生死循环的&#xff0c;导致CPU利用率接近100%。为了解决该问题&#xff0c;提供了Hashtable和Collections.synchronizedMap(hashM…

小米教你:2GB内存搞定20亿数据的高效算法

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! Hello,大家好!我是小米,今天要和大家聊聊一个非常有意思的算法实战问题——在2GB内存中,如何在20亿个整数中找到出现次数最多的数。这个问题涉及到大…

Three.js利用webgl着色器控制顶点位置打造波浪形状

<template> </template><script setup> import * as THREE from three import gsap from gsap //导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls // 导入 dat.gui import { GUI } from three/addons/libs/lil-gui…