• 认真地记录技术中遇到的坑!

C++中的各种可调用对象

C/C++ 悠悠 7个月前 (04-24) 286次浏览 0个评论

概述

一组执行任务的语句都可以视为一个函数,一个可调用对象。在程序设计的过程中,我们习惯于把那些具有复用性的一组语句抽象为函数,把变化的部分抽象为函数的参数。

函数的使用能够极大的极少代码重复率,提高代码的灵活性。

C++中具有函数这种行为的方式有很多。就函数调用方式而言

func(param1, param2);

这儿使用func作为函数调用名,param1param2为函数参数。在C++中就func的类型,可能为:

  • 普通函数
  • 类成员函数
  • 类静态函数
  • 仿函数
  • 函数指针
  • lambda表达式 C++11加入标准
  • std::function C++11加入标准

下面就这几种函数展开介绍

简单函数形式

普通函数

这种函数定义比较简单,一般声明在一个文件开头。如下:

#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;} 

其中:

  • [captures]为捕获列表,用于捕获外层变量
  • (params)为匿名函数参数列表
  • ->return_type指定匿名函数返回值类型
  • { statments; }部分为函数体,包括一系列语句

需要注意:

  • 当匿名函数没有参数时,可以省略(params)部分
  • 当匿名函数体的返回值只有一个类型或者返回值为void时,可以省略->return_type部分
  • 定义匿名函数时,一般使用auto作为匿名函数类型

下面都是有效的匿名函数定义

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函数中使用外部作用域中的变量,需要在[]中指定使用哪些变量。

下面是各种捕获选项:

  • [] 不捕获任何变量
  • [&] 捕获外部作用域中所有变量,并作为引用在匿名函数体中使用
  • [=] 捕获外部作用域中所有变量,并拷贝一份在匿名函数体中使用
  • [x, &y] x按值捕获, y按引用捕获
  • [&, x] x按值捕获. 其它变量按引用捕获
  • [=, &y] y按引用捕获. 其它变量按值捕获
  • [this] 捕获当前类中的this指针,如果已经使用了&或者=就默认添加此选项

只有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能兼容带捕获