回调函数兼函数指针与仿函数(谓词形式概论)

目录

前言

一、回调函数

1)背景引入

2)函数指针

3)回调函数

4)应用实例

(1)qsort() 库函数介绍

(2)void* 指针

(3)qsort() 调用实现 <==> 函数指针案例

二、仿函数

1)概念介绍

2)仿函数作函数参数(以 find_if() 函数为例)

三、函数指针与仿函数对比

1)两者异同点

2)充当谓词的形式讨论

3)普通类内函数作谓语

(1)类内函数直接充当谓语讨论

(2)利用lambda表达式调用类内函数

(3)利用bind绑定语句实现

(4)利用function模板实现

总结


前言

        一个好的软件设计应该具有高内聚和低耦合的特点。高内聚意味着模块内部的功能联系紧密,一个模块应该专注于完成一项具体的任务。低耦合表示模块之间的连接和依赖关系较少,模块之间的接口简单且数据交换有限。通过高内聚和低耦合的设计,可以提高软件的可重用性和移植性。高内聚使得模块可以独立地进行开发、测试和维护,而不会对其他模块产生过多的影响。低耦合减少了模块之间的依赖,使得模块可以更容易地被替换、重用或移植到其他系统中。所以,一个好的软件设计应当追求高内聚低耦合。这样的设计能够提高代码的可维护性、可测试性和可扩展性,降低代码的复杂性,并促进代码的重用和移植。要想高质量的实现高内聚低耦合编程,便不得不提回调函数(C语言)与仿函数(C++)的实际应用。

(注:回调函数适用于C/C++,而仿函数仅仅出现在C++语言中,读者可自行取舍阅读)


一、回调函数

1)背景引入

简单地,现在如果甲方需要你实现一个简易计算器功能,常见代码如下:

double Add(double a, double b)
{
	return a + b;
}
double Sub(double a, double b)
{
	return a - b;
}
double Mul(double a, double b)
{
	return a * b;
}
double Div(double a, double b)
{
	return a / b;
}
void low_calculate()
{
	double n1 = 0;
	double n2 = 0;
	int choose = 0;      // 1,2,3,4 分别代表 加减乘除
	double result = 0;
	printf("请依次输入两数:");
	scanf("%lf %lf", &n1, &n2);
	printf("请输入需要对两数进行的操作选择:(1,2,3,4 分别代表 加减乘除)\n");
	scanf("%d", &choose);
	switch (choose)
	{
	case 1:result = Add(n1,n2); break;
	case 2:result = Sub(n1,n2); break;
	case 3:result = Mul(n1,n2); break;
	case 4:result = Div(n1,n2); break;
	default: printf("cin of choose is error!");
	}
	printf("result = %lf\n", result);
}

运行测试:

可以看到成功实现了我们所需的功能,美中不足的是switch语句中每个case语句都对应相似的功能,与高内聚低耦合的编程素养相违背,那有没有办法可以提供一个总的仓库,不同的操作对应不同的接口,这样就可以仅仅通过函数参数的改变等方法就能实现不同功能,当后续新需求对应的功能需要写入时,就仅仅需要拓展接口即可,这一点可以类比C++中类的继承和多态特性进行理解。现在我说这种想法是可以实现的,代码是可以改进的,这里就用到了回调函数。

当然,我们铺垫一下预备知识 ------- 函数指针:

2)函数指针

定义:存放函数空间首地址的指针变量。

编译器在编译函数的时候,会为函数分配存储空间。确定内存中的存储空间后,我们可以通过获取该存储空间的首地址从而去获取到我们的函数片段。依靠指针可以完成对函数代码片段地址的定位,指向该首地址的指针变量,就叫做函数指针。

格式:

返回类型        (* 指针变量名称)        (参数)

int                (* ptFun)                (int, int)

代码中示例:

int Add(int a, int b)
{
	return a + b;
}
void test4()
{
	int a = 8, b = 7;
	int (*p_add)(int, int) = Add;       // 记得函数指针需要给 指针变量和* 加上括号
	int (*p_add_2)(int, int) = &Add;
	printf("Add %d with %d by p_add is : %d\n", a, b, p_add(a, b));
	printf("Add %d with %d by p_add is : %d\n", a, b, (*p_add)(a, b));
	printf("Add %d with %d by p_add_2 is : %d\n", a, b, p_add_2(a, b));
	printf("Add %d with %d by p_add_2 is : %d\n", a, b, (*p_add_2)(a, b));
}

运行结果:

现在来剖析上面代码:

  • 函数名 和 &函数名都对应函数在内存中的地址
  • 利用函数指针调用时:

        (1)可以直接使用指针变量寻址调用实现函数功能

        (2)也可以对指针变量解引用使用函数功能

需要注意的是函数指针变量的定义格式,记住函数指针也是指针。

3)回调函数

定义:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

文字晦涩难懂,我们接着来看上面计算器的功能实现:

double Calculate(double a, double b, double (*p)(double,double))  // 利用函数指针实现回调函数
{
	return p(a, b);
}

double (*p_c)(double, double) = NULL;
switch (choose)
{
case 1:p_c = Add; break;
case 2:p_c = Sub; break;
case 3:p_c = Mul; break;
case 4:p_c = Div; break;
default: printf("p_c is nullptr!");
}

double result = Calculate(n1, n2, p_c);  // 调用回调函数

可以看到我们把每次case中都要繁琐输入函数参数的步骤省略,不管哪种需求,case语句仅仅执行对函数指针变量p_c的赋值,只需要最后调用一次计算功能函数,将函数指针以参数形式传入,就可实现相似的所有计算功能,这就是回调函数的应用体现。同时,如果后续有其他业务拓展需要,也只需要新定义一个函数,同时插入一个新的case语句即可,无需改动 Calculate() 函数

4)应用实例

(1)qsort() 库函数介绍

比如我们想要利用库函数 qsort() 实现一个自定义快速排序,在调用时我们需要知道函数传参的类型,追溯函数声明,可以看到参数列表如下:

_ACRTIMP void __cdecl qsort(
    _Inout_updates_bytes_(_NumOfElements * _SizeOfElements) void*  _Base,
    _In_                                                    size_t _NumOfElements,
    _In_                                                    size_t _SizeOfElements,
    _In_                _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction
    );

函数 qsort() 包含四个参数:

该图片引用自(cplusplus.com网站)

注意:上面参数列表中第一个参数void* base是指向数组首元素的指针,不是指向整个数组的指针

当我们需要使用该函数时,发现第四个参数(指向比较两元素大小的指针)到底指的是什么?

因为排序包括升序和降序,而 qsort() 库函数不仅可以对内置类型排序,还可以对结构体变量等自定义类型排序,所以第四个参数 _CompareFunction 就是指定函数以什么规则进行排序,该规则需要利用传入函数指针的方式实现。

特别小心第四个参数函数指针指向的函数构建是有特殊要求的,比如返回值为 int 型,函数定义时两个函数参数都必须为 const void* 类型,这里就不得不提 void* 指针的用法,先看看 void* 指针的特性:

(2)void* 指针

void* 指针是空指针类型,可用于存储各种类型指针指向的地址,但是不能对其直接访问! 

void test1()
{
	int a = 14;
	void* p_v = &a;
	int* p_i = &a;
	printf("%d\n", *p_i);
	//printf("%d\n", *p_v);   // 编译报错
	printf("%d\n", *(int*)p_v);   // void* 指针在访问时必须强转为原有类型的指针再访问
}

如果没有对 void* 指针强转直接访问,编译时就会报错:

所以我们明确了 void* 指针仅用于存储不可直接访问,但是可以存储各类指针。

(3)qsort() 调用实现 <==> 函数指针案例

这样我们可以接着定义一个 qsort() 函数第四个参数所需的函数指针指向的函数,限制条件为返回值为 int ,函数参数为两个 void* 指针指向要比较的元素,函数名自拟,那么有如下示例代码:

int Compare_int_up(const void* a, const void* b)
{
	return *(int*)a - *(int*)b;   // 注意传入的是void*指针 访问要先强转类型再解引用
}

写个测试函数调用 qsort() 函数,并将上面函数的地址传给 qsort() 函数的第四个参数:

void test2()
{
	int arr[] = { 1,4,3,8,5,2,7,6,9 };
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), Compare_int_up);
	print_arr(arr, sizeof(arr) / sizeof(arr[0]));
}

注意:函数名 和 &函数名都代表函数的地址(指针),两者没有任何区别

运行结果:

可以看到我们成功利用 qsort() 函数实现了升序排列,那降序排列呢,仅需要改变 qsort() 的第四个参数即函数指针指向函数的实现方法即可,同样的我们希望降序,所以构建新函数时返回值为正数代表新函数第二个参数大于第一个参数。按照该思路重新拟写 Compare_int_down() 函数,并调用测试,代码如下:

int Compare_int_down(const void* a, const void* b)
{
	return *(int*)b - *(int*)a;
}
void test2()
{
	int arr[] = { 1,4,3,8,5,2,7,6,9 };
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), Compare_int_down);
	print_arr(arr, sizeof(arr) / sizeof(arr[0]));
}

运行结果:

总之,通过此案例我们明确了回调函数需要利用函数指针实现,函数指针作为主调函数的参数,实际传给主调函数参数的函数指针本质上就是自定义的函数地址,因为C语言中指针就是地址

二、仿函数

1)概念介绍

        仿函数通常通过重载运算符()来实现,是定义了一个含有operator()成员函数的对象,可以视为一个一般的函数,只不过这个函数功能是在一个类中的运算符operator()中实现,是一个函数对象,它将函数作为参数传递的方式来使用。这是因为C++中的函数调用运算符operator()可以使得类对象像函数一样被调用,因此可以实现函数对象的行为

(注:仿函数是C++中常用的可以被调用并执行特定的操作的对象,建议有过耳染的读者选阅)

特点:

1.仿函数不是函数是类

2.仿函数一般重载了()运算符,拥有函数的行为

代码示例:

// 仿函数示例,判断一个整数是否为偶数
struct IsEven
{
    bool operator()(int num) const
    {
        return (num % 2 == 0);
    }
};

仿函数定义已经给出,具体使用将会在后面提及,需要注意仿函数返回值不一定是bool类型

C++风格仿函数及应用示例

class IsEven
{
public:
	bool operator()(int num)
	{
		if (num % 2 == 0) { return true; }
		return false;
	}
};
void test3()
{
	IsEven is_even;
	vector<int> vec = { 1,7,3,8,5,2,8,6,9 };

	// 使用仿函数作为谓词判断偶数
	vector<int>::iterator it = find_if(vec.begin(), vec.end(), is_even);
	if (it != vec.end())
	{
		cout << "first even num is: " << *it << endl;
	}
	else
	{
		cout << "no even num in vec: " << endl;
	}
}

需要通晓上面代码的语法,需要我们明晰实例中 find_if() 函数的用法,所以先讲解 find_if() 函数用法,以该函数为例,深入理解其他同类型需要用到仿函数做谓语的函数。

2)仿函数作函数参数(以 find_if() 函数为例)

_EXPORT_STD template <class _InIt, class _Pr>
_NODISCARD _CONSTEXPR20 _InIt find_if(_InIt _First, const _InIt _Last, _Pr _Pred) { // find first satisfying _Pred
    _Adl_verify_range(_First, _Last);
    auto _UFirst      = _Get_unwrapped(_First);
    const auto _ULast = _Get_unwrapped(_Last);
    for (; _UFirst != _ULast; ++_UFirst) {
        if (_Pred(*_UFirst)) {
            break;
        }
    }

    _Seek_wrapped(_First, _UFirst);
    return _First;
}

发现 find_if() 函数共三个参数,前两个分别代表迭代器指向的起始位置和最终位置,当然,在个人初阶编程时可以将迭代器近似看作指针使用。第三个参数即为该函数需要指定的谓语对应类型一般可以是函数指针函数对象,这里的函数对象即为仿函数。

该图片引用自(cplusplus.com网站)

所以这样就不难分析出上面C++示例代码中 find_if() 的调用需求,至于实现逻辑源码也已经给出,可以自行参阅。既然对谓语的解释可以用仿函数也可以用函数指针,那我们仿照仿函数实现内容写个同样的普通函数,再将普通函数的指针传给 find_if() 函数,如果可以实现这不就是回调函数吗,现在就测试实现,看下面代码:

//class IsEven
//{
//public:
//	bool operator()(int num)
//	{
//		if (num % 2 == 0) { return true; }
//		return false;
//	}
//};
bool is_even_num(int num)
{
	if (num % 2 == 0) { return true; }
	return false;
}
void test3()
{
	IsEven is_even;
	vector<int> vec = { 1,7,3,8,5,2,8,6,9 };

	// 使用仿函数作为谓词判断偶数
	//vector<int>::iterator it = find_if(vec.begin(), vec.end(), is_even);  // 谓语为仿函数
	vector<int>::iterator it = find_if(vec.begin(), vec.end(), is_even_num); // 谓语为函数指针
	
    if (it != vec.end())
	{
		cout << "first even num is: " << *it << endl;
	}
	else
	{
		cout << "no even num in vec: " << endl;
	}
}

编译没有报错,运行结果:

我们发现利用函数指针充当谓语同样可以实现仿函数指定的功能,那仿函数还要额外定义类然后创建局部变量,是不是函数指针优于仿函数,仿函数没有使用的必要呢?

三、函数指针与仿函数对比

1)两者异同点

共同点:

  1. 都用于实现函数对象,即将函数作为对象来处理。
  2. 可以作为函数的参数传递或作为函数的返回值。
  3. 可以在特定上下文中被调用,以执行特定的操作或计算。

区别:

  1. 语法和调用方式上的区别:
    • 函数指针通过指针调用方式调用函数,类似于普通函数的调用。
    • 仿函数则通过类的对象调用方式调用函数,类似于调用一个重载了operator()的类成员函数。
  2. 可扩展性和灵活性上的区别:
    • 仿函数是一个类,可以包含成员变量和成员函数,可以存储额外的状态信息,并且可以重载多个函数调用运算符,使其具有不同的行为,从而实现更复杂的功能。
    • 函数指针只能指向函数,不能存储额外的状态信息,也无法重载运算符。
  3. 面向对象特性上的区别:
    • 仿函数是面向对象的,可以使用继承、多态等面向对象特性。
    • 函数指针是面向过程的,无法使用面向对象的特性。

下面,首先给出一段简单代码以便深刻理解上面总结内容中调用方式区别

// 函数指针示例
int add(int a, int b)
{
	return a + b;
}

// 仿函数示例
class AddFunctor
{
public:
	int operator()(int a, int b) const
	{
		return a + b;
	}
};

void test4()
{
	// 使用函数指针
	int (*fp)(int, int) = add;
	int result1 = fp(2, 3);
	std::cout << "Function Pointer Result: " << result1 << std::endl;

	// 使用仿函数
	AddFunctor functor;
	int result2 = functor(2, 3);
	std::cout << "Functor Result: " << result2 << std::endl;
}

运行结果:

通过上面例子可以更清晰的认识到两者调用方式的区别:

  • 函数指针通过指针调用方式调用函数,类似于普通函数的调用。
  • 仿函数则通过类的对象调用方式调用函数。

同时该示例我们可以看到仿函数的类内重载()操作运算符的成员函数,其返回值并非bool类型。事实上,仿函数的返回值类型可以自行指定,但是当该仿函数用于谓语时,大多数情况返回值都是bool类型。

2)充当谓词的形式讨论

注意:充当谓词的函数可声明为static类型,这样使用时可以直接通过类调用静态成员函数使用或充当谓词,但是仿函数不能声明为static类型。

下面给出用法示例:

class IsEven
{
public:
	static bool operator()(int num)
	{
		if (num % 2 == 0) { return true; }
		return false;
	}
	static bool is_even_num_member(int num)
	{
		if (num % 2 == 0) { return true; }
		return false;
	}
};
bool is_even_num(int num)
{
	if (num % 2 == 0) { return true; }
	return false;
}
void test3()
{
	IsEven is_even;
	vector<int> vec = { 1,7,3,8,5,2,8,6,9 };

	// 使用仿函数作为谓词判断偶数
	//vector<int>::iterator it = find_if(vec.begin(), vec.end(), is_even);
	vector<int>::iterator it = find_if(vec.begin(), vec.end(), IsEven::is_even_num_member);
	//vector<int>::iterator it = find_if(vec.begin(), vec.end(), is_even_num);
	if (it != vec.end())
	{
		cout << "first even num is: " << *it << endl;
	}
	else
	{
		cout << "no even num in vec: " << endl;
	}
}

以上代码是无法通过编译的,因为重载用于仿函数的()运算符函数时,将其声明为static类型不符合C++语法,重载函数调用运算符 operator() 的目的是将对象像函数一样调用,使其具有可调用对象的行为。为了实现这个行为,C++ 语言规定,只有非静态成员函数才可以直接访问对象的成员变量和其他非静态成员函数

所以我们选择将重载运算符函数注释掉,仅保留同样实现功能的类成员函数 static bool is_even_num_member(int num) ,注意这里我们将其声明为static类型,调用该函数并运行:

class IsEven
{
public:
	static bool is_even_num_member(int num)
	{
		if (num % 2 == 0) { return true; }
		return false;
	}
};
void test3()
{
	vector<int> vec = { 1,7,3,8,5,2,8,6,9 };

	// 使用仿函数作为谓词判断偶数
	vector<int>::iterator it = find_if(vec.begin(), vec.end(), IsEven::is_even_num_member);

	if (it != vec.end())
	{
		cout << "first even num is: " << *it << endl;
	}
	else
	{
		cout << "no even num in vec: " << endl;
	}
}

运行结果:

实际上,利用static静态类内局部函数作谓词时,其本质上属于仿函数或函数指针作谓词的范畴,具体取决于语境。当然利用静态局部函数直接作谓词,在调用时直接通过类调用,没有类对象的产生,并不违背函数指针调用的原则,与仿函数则通过类的对象调用方式调用函数有直接区别;但是另一方面静态局部函数可以像函数一样调用并且可以在类内组合进行其他复杂操作,这又与仿函数特征相吻合

        从函数指针的角度来看,静态局部函数名可以被解释为函数的指针。在将静态局部函数作为谓词传递给算法函数时,实际上是将函数的指针传递给了算法函数,这种使用方式类似于函数指针做谓词。

        从仿函数的角度来看,静态局部函数可以被视为函数对象。尽管它不是类对象,但它具备仿函数的特性:可以像函数一样调用,可以存储状态(如果需要),可以作为算法函数的谓词传递等。

需要特别注意的是,仿函数一般通过重载运算符实现,但是普遍特征是函数内嵌在类中,这是仿函数的突出特征!


通过上面内容我们分析了不同函数作谓语的情形:

(1)全局函数作谓语 ------ 通过指针方式调用函数

(2)重载()运算符作谓语(仿函数)------ 通过类的对象调用方式调用函数

(3)类内局部静态函数作谓语 ------ 通过指针方式或类的对象调用方式调用函数

分别对应的充当谓语语句对比:

find_if(vec.begin(), vec.end(), is_even_num);   // 利用全局函数
find_if(vec.begin(), vec.end(), is_even);   // 利用仿函数(重载运算符实现)
find_if(vec.begin(), vec.end(), IsEven::is_even_num_member);   // 利用静态局部函数

显然,我们少了一种情况,那类内局部函数可以作谓语吗?

3)普通类内函数作谓语

(1)类内函数直接充当谓语讨论

        首先我们考虑类内普通函数作谓语时,其不能像静态函数那样用类直接调用,首先必须创建一个类对象,再用类对象才能调用类内函数。这样看来和仿函数的使用很像,同样必须先创建类对象,所以说它应该归并为仿函数一类吗,毕竟仿函数作谓语的特征就是通过类的对象调用方式调用函数。

        再者我们调用形式应该是怎样的,谓语接受函数指针、仿函数、lambda表达式、std::function 对象、函数指针 + std::bind 几种常见类型。既然普通类内函数具有和对象绑定的关系,并非和类之间一 一绑定,所以谓词中实参部分如果使用函数指针进行对应的话必然要有创建的类对象调用出特定函数。

所以我们设想一种传参方式,将指针传给谓语部分:

class IsEven
{
public:
	bool is_even_num_member_nostatic(int num)
	{
		if (num % 2 == 0) { return true; }
		return false;
	}
};
void test3()
{
	IsEven is_even;
	vector<int> vec = { 1,7,3,8,5,2,8,6,9 };

	// 定义函数指针类型
	typedef bool (IsEven::* MemberFunctionPtr)(int);
	MemberFunctionPtr p = &IsEven::is_even_num_member_nostatic;   // 利用指针p指向局部函数

	vector<int>::iterator it = find_if(vec.begin(), vec.end(), p);
    
    ......
}

上面这种方式我们并没有使用实例化的类对象,试着直接定义指针指向类内非静态函数,虽然通过类直接访问这种方式只能用于静态函数,但是编译没报错,试试就逝世,运行结果:

运行报错,说明这种小心思在运行时会被打回原形,所以得另辟蹊径,重新遵循一开始的分析,非静态局部函数的调用必然要由类对象来进行,那直接调用会怎么样,接着看:

//vector<int>::iterator it = find_if(vec.begin(), vec.end(), is_even);    // 利用仿函数
vector<int>::iterator it = find_if(vec.begin(), vec.end(), is_even.is_even_num_member_nostatic);

可以看到我们下面直接利用类对象调用的局部函数操作,像极了类内重载()运算符时构建的仿函数调用,甚至有些画蛇添足,明明可以直接用函数对象作谓语参数,这里还多利用对象调用一次函数,不美妙一点都不美妙,因为编译就报错喽:

好好好,那加上括号呢?

那参数填啥?  无底洞,换其他方法试着解决吧。

(2)利用lambda表达式调用类内函数

根据上面提到的谓词接受的除了仿函数和函数指针,还有lambda表达式、bind 绑定语句等,所以我们直接抛开前两者,直接先从lambda表达式入手,利用其参数列表可以捕获外部变量的特性,刚好可以在内部利用捕获到的类对象实现调用,实现方法如下:

IsEven is_even;
vector<int> vec = { 1,7,3,8,5,2,8,6,9 };
vector<int>::iterator it = find_if(vec.begin(), vec.end(), [&](int num) {        // 利用lambda表达式实现
	return is_even.is_even_num_member_nostatic(num);
	});
......

运行结果:

可以看到我们成功使用lambda表达式作算法函数谓语,得到了预期的结果。

(3)利用bind绑定语句实现

首先简述 bind() 语句的定义:

std::bind() 是一个函数模板,它可以用于将成员函数与对象进行绑定,创建一个可调用对象(函数对象)

std::bind() 的作用是部分应用(Partial Application)和函数适配(Function Adaption)。它可以将一个函数的部分参数固定下来,返回一个新的可调用对象,该对象可以像原始函数一样被调用,但具有预先绑定的参数

该图片引用自(cplusplus.com网站)

到这里我们能够基本懂得 bind() 语句的用法,当我们将类对象(is_even)和类内普通非静态函数(is_even_num_member_nostatic)绑定,考虑到该函数有一个参数,实现这里的谓语需要接受参数,故需要一个占位符(placeholders),所以实现方法如下:

IsEven is_even;
vector<int> vec = { 1,7,3,8,5,2,8,6,9 };
auto boundFunction = std::bind(&IsEven::is_even_num_member_nostatic, &is_even, std::placeholders::_1);    // 使用 bind 绑定成员函数和对象
vector<int>::iterator it = find_if(vec.begin(), vec.end(), boundFunction);
......

(注:切记包含头文件 <functional> )

运行结果:

显然,利用bind语句同样可以高质量的实现需求,同时印证了谓语可以由多种形式充当,具体的使用形式和实现方法需要视情况灵活使用。

(4)利用function模板实现

简要看看function模板的调用实现方法:

该图片引用自(cplusplus.com网站)

来看实现代码:

IsEven is_even;
vector<int> vec = { 1,7,3,8,5,2,8,6,9 };
function<bool(int)> functionObject = [&](int num) {        // 使用 std::function 来创建可调用对象
	return is_even.is_even_num_member_nostatic(num);
	};
auto it = std::find_if(vec.begin(), vec.end(), functionObject);
......

可以看到该实现方法本质上还是对lambda表达式进行包装,利用function包装后的函数对象传给算法函数充当谓语,接着看运行结果:

可以看到结果符合要求。

其实,利用function还有一种与bind语句组合使用的方法,这里用处不大反而繁琐,故只给出实现代码同时不再过多解释,代码如下:

bool is_even_helper(IsEven& is_even, int num)     // 构建一个辅助函数
{
    return is_even.is_even_num_member_nostatic(num);
}
void test3()
{
    IsEven is_even;
    std::vector<int> vec = { 1,7,3,8,5,2,8,6,9 };
    
    // 使用 std::function 来创建可调用对象
    function<bool(int)> functionObject = std::bind(is_even_helper, std::ref(is_even), std::placeholders::_1);
    
    // 使用函数对象作为谓词
    auto it = std::find_if(vec.begin(), vec.end(), functionObject);
    ......
}

总结

        本文探讨了回调函数的概念、函数指针和仿函数作为实现回调功能的常见方式。回调函数在C++中被广泛应用,可以实现灵活的功能和扩展性。函数指针是一种简单直接的方式,它通过将函数地址作为参数来实现回调。而仿函数则一般是通过重载函数调用运算符(),使得对象可以像函数一样被调用,为回调提供了更多的灵活性和可定制性。

        在回调函数的应用实例中,我们介绍了qsort()库函数,并通过使用函数指针作为回调函数来实现自定义的排序功能。同时,我们也探讨了void*指针的使用,以处理不同类型的数据。

        另外,我们还介绍了仿函数作为回调函数的使用方法,特别是以find_if()算法函数为例。仿函数可以方便地与STL算法函数配合使用,提供更加灵活的查找功能。

        最后,我们对函数指针和仿函数进行了对比。函数指针相对简单直观,但在灵活性和可维护性方面可能有一些局限性。而仿函数则更为灵活,可以根据需求自定义行为,并且可以方便地与其他函数对象配合使用。特别纠正了仿函数只是多数情况下通过重载()运算符实现,类内静态函数充当谓语等其他方式同样属于仿函数实现的范畴

        综上所述,根据具体需求和个人偏好,选择适合的方式来实现回调函数功能。深入理解和灵活运用函数指针和仿函数的概念,将有助于编写出扩展性强、可维护性高的代码。当然本文可能受限于知识水平存在部分不严谨的部分,恳请读者批评指正以呈现更健硕的详解体系。

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

螺蛳粉只吃炸蛋的走风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值