时间:2021-05-20
概述
一组执行任务的语句都可以视为一个函数,一个可调用对象。在程序设计的过程中,我们习惯于把那些具有复用性的一组语句抽象为函数,把变化的部分抽象为函数的参数。
函数的使用能够极大的极少代码重复率,提高代码的灵活性。
C++中具有函数这种行为的方式有很多。就函数调用方式而言
func(param1, param2);
这儿使用func作为函数调用名,param1和param2为函数参数。在C++中就func的类型,可能为:
下面就这几种函数展开介绍
简单函数形式
普通函数
这种函数定义比较简单,一般声明在一个文件开头。如下:
#include <iostream>// 普通函数声明和定义int func_add(int a, int b) { return a + b; }int main(){ int a = 10; int b = 20; int sum = func_add(a, b); std::cout << a << "+" << b << "is : " << sum << std::endl; getchar(); return 0;}类成员函数
在一个类class中定义的函数一般称为类的方法,分为成员方法和静态方法,区别是成员方法的参数列表中隐含着类this指针。
#include <iostream>class Calcu{public: int base = 20; // 类的成员方法,参数包含this指针 int class_func_add(const int a, const int b) const { return this->base + a + b; }; // 类的静态成员方法,不包含this指针 static int class_static_func_add(const int a, const int b) { return a + b; };};int main(void) { Calcu obj; int a = 10; int b = 20; // 类普通成员方法调用如下 obj.class_func_add(a, b); // 类静态成员方法调用如下 obj.class_static_func_add(a, b); Calcu::class_static_func_add(a, b); getchar(); return 0;}仿函数
仿函数是使用类来模拟函数调用行为,我们只要重载一个类的operator()方法,即可像调用一个函数一样调用类。这种方式用得比较少。
class ImitateAdd{public: int operator()(const int a, const int b) const { return a + b; };};int main(){ // 首先创建一个仿函数对象,然后调用()运算符模拟函数调用 ImitateAdd imitate; imitate(5, 10); getchar(); return 0;}函数指针
顾名思义,函数指针可以理解为指向函数的指针。可以将函数名赋值给相同类型的函数指针,通过调用函数指针实现调用函数。
函数指针是标准的C/C++的回调函数的使用解决方案,本身提供了很大的灵活性。
#include <iostream>// 声明一个compute函数指针,函数参数为两个int型,返回值为int型int (*compute)(int, int);int max(int x, int y) { return x >= y ? x : y; }int min(int x, int y) { return x <= y ? x : y; }int add(int x, int y) { return x + y; }int multiply(int x, int y) { return x * y; }// 一个包含函数指针作为回调的函数int compute_x_y(int x, int y, int(*compute)(int, int)){ // 调用回调函数 return compute(x, y);}int main(void) { int x = 2; int y = 5; std::cout << "max: " << compute_x_y(x, y, max) << std::endl; // max: 5 std::cout << "min: " << compute_x_y(x, y, min) << std::endl; // min: 2 std::cout << "add: " << compute_x_y(x, y, add) << std::endl; // add: 7 std::cout << "multiply: " << compute_x_y(x, y, multiply) << std::endl; // multiply: 10 // 无捕获的lambda可以转换为同类型的函数指针 auto sum = [](int x, int y)->int{ return x + y; }; std::cout << "sum: " << compute_x_y(x, y, sum) << std::endl; // sum: 7 getchar(); return 0;}Lambda函数
Lambda函数定义
Lambda函数,又可以称为Lambda表达式或者匿名函数,在C++11中加入标准。定义形式如下:
[captures] (params) -> return_type { statments;}
其中:
需要注意:
下面都是有效的匿名函数定义
auto func1 = [](int x, int y) -> int { return x + y; }; auto func2 = [](int x, int y) { return x > y; }; // 省略返回值类型auto func3 = [] { global_ip = 0; }; // 省略参数部分Lambda函数捕获列表
为了能够在Lambda函数中使用外部作用域中的变量,需要在[]中指定使用哪些变量。
下面是各种捕获选项:
只有lambda函数没有指定任何捕获时,才可以显式转换成一个具有相同声明形式函数指针
auto lambda_func_sum = [](int x, int y) { return x + y; }; // 定义lambda函数void (*func_ptr)(int, int) = lambda_func_sum; // 将lambda函数赋值给函数指针func_ptr(10, 20); // 调用函数指针std::function函数包装
std::function定义
std::function在C++11后加入标准,可以用它来描述C++中所有可调用实体,它是是可调用对象的包装器,声明如下:
#include <functional>// 声明一个返回值为int,参数为两个int的可调用对象类型std::function<int(int, int)> Func;使用之前需要导入<functional>库,并且通过std命名空间使用。
其他函数实体转化为std::function
std::function强大的地方在于,它能够兼容所有具有相同参数类型的函数实体。
相比较于函数指针,std::function能兼容带捕获的lambda函数,而且对类成员函数提供支持。
#include <iostream>#include <functional>std::function<int(int, int)> SumFunction;// 普通函数int func_sum(int a, int b){ return a + b;}class Calcu{public: int base = 20; // 类的成员方法,参数包含this指针 int class_func_sum(const int a, const int b) const { return this->base + a + b; }; // 类的静态成员方法,不包含this指针 static int class_static_func_sum(const int a, const int b) { return a + b; };};// 仿函数class ImitateAdd{public: int operator()(const int a, const int b) const { return a + b; };};// lambda函数auto lambda_func_sum = [](int a, int b) -> int { return a + b; };// 函数指针int (*func_pointer)(int, int);int main(void) { int x = 2; int y = 5; // 普通函数 SumFunction = func_sum; int sum = SumFunction(x, y); std::cout << "func_sum:" << sum << std::endl; // 类成员函数 Calcu obj; SumFunction = std::bind(&Calcu::class_func_sum, obj, std::placeholders::_1, std::placeholders::_2); // 绑定this对象 sum = SumFunction(x, y); std::cout << "Calcu::class_func_sum:" << sum << std::endl; // 类静态函数 SumFunction = Calcu::class_static_func_sum; sum = SumFunction(x, y); std::cout << "Calcu::class_static_func_sum:" << sum << std::endl; // lambda函数 SumFunction = lambda_func_sum; sum = SumFunction(x, y); std::cout << "lambda_func_sum:" << sum << std::endl; // 带捕获的lambda函数 int base = 10; auto lambda_func_with_capture_sum = [&base](int x, int y)->int { return x + y + base; }; SumFunction = lambda_func_with_capture_sum; sum = SumFunction(x, y); std::cout << "lambda_func_with_capture_sum:" << sum << std::endl; // 仿函数 ImitateAdd imitate; SumFunction = imitate; sum = SumFunction(x, y); std::cout << "imitate func:" << sum << std::endl; // 函数指针 func_pointer = func_sum; SumFunction = func_pointer; sum = SumFunction(x, y); std::cout << "function pointer:" << sum << std::endl; getchar(); return 0;}最后的输出如下:
func_sum:7
Calcu::class_func_sum:27
Calcu::class_static_func_sum:7
lambda_func_sum:7
lambda_func_with_capture_sum:17
imitate func:7
function pointer:7
其中需要注意对于类成员函数,因为类成员函数包含this指针参数,所以单独使用std::function是不够的,还需要结合使用std::bind函数绑定this指针以及参数列表。
std::bind参数绑定规则
在使用std::bind绑定类成员函数的时候需要注意绑定参数顺序:
// 承接上面的例子SumFunction = std::bind(&Calcu::class_func_sum, obj, std::placeholders::_1, std::placeholders::_2);SumFunction(x, y);看下面的例子:
// 绑定成员函数第一个参数为4,第二个参数为6SumFunction = std::bind(&Calcu::class_func_sum, obj, 4, 6);SumFunction(); // 值为 10// 绑定成员函数第一个参数为调用时的第一个参数,第二个参数为10SumFunction = std::bind(&Calcu::class_func_sum, obj, std::placeholders::_1, 10);SumFunction(5); // 值为 15// 绑定成员函数第一个参数为调用时的第二个参数,第一个参数为调用时的第二个参数SumFunction = std::bind(&Calcu::class_func_sum, obj, std::placeholders::_2, std::placeholders::_1);SumFunction(5, 10); // 值为 15对于非类成员对象,一般直接赋值即可,会自动进行转换并绑定参数,当然也可以使用std::bind指定参数绑定行为;
#include <iostream>#include <functional>// 按照顺序输出x, y, xvoid print_func(int x, int y, int z){ std::cout << x << " " << y << " " << z << std::endl;}std::function<void(int, int, int)> Func;int main(){ Func = std::bind(&print_func, 1, 2, 3); Func(4, 5, 6); // 输出: 1 2 3 Func = std::bind(&print_func, std::placeholders::_2, std::placeholders::_1, 3); Func(1, 2, 7); // 输出: 2 1 3 int n = 10; Func = std::bind(&print_func, std::placeholders::_1, std::placeholders::_1, n); Func(5, 6, 7); // 输出: 5 5 10 getchar(); return 0;}需要注意:就算是绑定的时候指定了默认参数,在调用的时候也需要指定相同的参数个数(虽然不会起作用),否则编译不通过。
关于回调函数
回调就是通过把函数等作为另外一个函数的参数的形式,在调用者层指定被调用者行为的方式。
通过上面的介绍,我们知道,可以使用函数指针,以及std::function作为函数参数类型,从而实现回调函数:
#include <iostream>#include <functional>std::function<int(int, int)> ComputeFunction;int (*compute_pointer)(int, int);int compute1(int x, int y, ComputeFunction func) { // do something return func(x, y);}int compute2(int x, int y, compute_pointer func){ // do something return func(x, y);}// 调用方式参考上面关于函数指针和std::function的例子以上两种方式,对于一般函数,简单lambda函数而言是等效的。
但是如果对于带有捕获的lambda函数,类成员函数,有特殊参数绑定需要的场景,则只能使用std::function。
其实还有很多其他的实现回调函数的方式,如下面的标准面向对象的实现:
#include <iostream>// 定义标准的回调接口class ComputeFunc{public: virtual int compute(int x, int y) const = 0;};// 实现回调接口class ComputeAdd : public ComputeFunc{public: int compute(int x, int y) const override { return x + y; }};int compute3(int x, int y, const ComputeFunc& compute){ // 调用接口方法 return compute.compute(x, y);}// 调用方法如下int main(){ ComputeAdd add_func; // 创建一个调用实例 int sum = compute3(3, 4, add_func); // 传入调用实例}面向对象的方式更加灵活,因为这个回调的对象可以有很复杂的行为。
以上三种方法各有各的好处,根据你需要实现的功能的复杂性,扩展性和应用场景等决定使用。
另外,这些函数类型的参数可能为空,在调用之前,应该检查是否可以调用,如检查函数指针是否为空。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
本文实例讲解了C++中基类和派生类之间的转换。对于深入理解C++面向对象程序设计有一定的帮助作用。此处需要注意:本文实例讲解内容的前提是派生类继承基类的方式是公
C++中const对象与const成员函数的实例详解const对象只能调用const成员函数:#includeusingnamespacestd;classA{
反射调用返回复杂对象的.NET方法定义数据接口上一篇在C++中反射调用.NET(一)中,我们简单的介绍了如何使用C++/CLI并且初步使用了反射调用.NET程序
c++中,临时对象一旦不需要,就会调用析构函数,释放其占有的资源;而具名对象则是与创建的顺序相反,依次调用析构函数。c++源码:复制代码代码如下:classX{
C++析构函数创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作(例如回收创建对象时消耗的各种资源),这个函