【C++ 实现函数对象】

1.作用

lambda 表达式、仿函数、函数指针都是 C++ 中实现函数对象的方式,可以在 STL 中的算法函数中使用。它们的共通作用主要有以下几个方面:

实现函数对象。lambda 表达式、仿函数、函数指针都可以实现函数对象,可以根据需要定义不同的行为。例如,可以定义一个 lambda 表达式来实现排序操作,或者定义一个仿函数来实现加法操作。

提高代码的可读性和可维护性。使用 lambda 表达式、仿函数、函数指针可以将函数对象直接传递给算法函数,避免了定义额外的函数和类,提高了代码的可读性和可维护性。

简化代码的编写和调试。使用 lambda 表达式、仿函数、函数指针可以简化代码的编写和调试过程,避免了繁琐的函数和类定义,提高了开发效率和代码质量。

实现函数对象的复用。lambda 表达式、仿函数、函数指针都可以实现函数对象的复用,可以在不同的上下文中使用。例如,可以将一个 lambda 表达式作为参数传递给多个算法函数,或者将一个仿函数作为成员变量嵌入到一个类中,实现复杂的操作。

需要注意的是,lambda 表达式、仿函数、函数指针的实现方式和语法不同,但实现的功能和作用类似。在选择使用 lambda 表达式、仿函数、函数指针时,需要考虑到具体的需求和上下文,并选择合适的实现方式。

2.lambda 表达式

Lambda表达式是一种匿名函数,可以在需要函数对象的任何地方使用。它的语法类似于函数定义,但是没有函数名,可以用于定义一次性的函数对象。

Lambda表达式的基本语法如下:

[capture list] (parameter list) -> return type { function body }

其中,capture list 用于捕获变量,可以为空;parameter list 是参数列表,可以为空;return type 是返回值类型,可以为空;function body 是函数体。

Lambda表达式的捕获列表用于指定Lambda表达式中使用的外部变量。捕获列表可以为空,也可以包含一个或多个捕获项,每个捕获项都由一个捕获符号和一个变量组成。捕获符号可以是 & 或 =,分别表示按引用捕获和按值捕获。例如:

int a = 1;
auto lambda = [=]() { return a + 1; };

上面的代码中,Lambda表达式按值捕获了变量 a,并返回 a+1。

Lambda表达式可以在需要函数对象的任何地方使用,例如作为函数参数、返回值、变量等。以下是一个使用Lambda表达式的示例代码:

#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    // 创建一个包含多个整数的向量
    std::vector<int> numbers = { 1, 3, 2, 5, 4 };
    // 使用 Lambda 表达式对向量进行排序
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) { return a < b; });
    // 输出排序后的向量
    for (auto number : numbers) {
        std::cout << number << " ";
    }
    std::cout << std::endl;
    return 0;
}

在上面的代码中,我们创建了一个包含多个整数的向量,并使用 std::sort 函数对其进行排序。在排序过程中,我们使用了一个 Lambda 表达式,其中按值捕获了两个整数 a 和 b,并返回 a<b,用于指定排序的规则。

需要注意的是,Lambda表达式可以捕获外部变量,但是对于静态局部变量和全局变量,建议使用常量指针来进行捕获,避免由于生命周期问题导致访问无效内存。同时,Lambda表达式的返回值类型可以省略,编译器会自动推导出返回值类型。

以下是7种Lambda表达式中捕获列表的解释,以及每种捕获方式的简单案例:

1.[]:空的捕获列表,表示不捕获任何变量。这种方式适用于只需要在Lambda表达式中使用本地变量或者全局变量的情况,不需要访问或者修改外部变量。例如:

int a = 1;
auto lambda = []() { return a + 1; };

在上面的代码中,Lambda表达式不需要访问任何外部变量,因此使用空的捕获列表。

2.[变量名1, 变量名2, …]:捕获指定的变量,可以是按值或按引用捕获。多个变量之间用逗号分隔。这种方式适用于只需要在Lambda表达式中使用特定的外部变量的情况。例如:

int a = 1, b = 2;
auto lambda = [a]() { return a + 1; 

在上面的代码中,Lambda表达式按值捕获了变量 a,并返回 a+1。

3.[=]:按值捕获所有外部变量。这种方式适用于需要在Lambda表达式内部访问外部变量,但是不需要修改它们的值的情况。例如:

int a = 1, b = 2;
auto lambda = [=]() { return a + b; };

在上面的代码中,Lambda表达式按值捕获了变量 a 和 b,并返回它们的和。

4.[&]:按引用捕获所有外部变量。这种方式适用于需要在Lambda表达式内部修改外部变量的值的情况。例如:

int a = 1, b = 2;
auto lambda = [&]() { a += b; };

在上面的代码中,Lambda表达式按引用捕获了变量 a 和 b,并将变量 a 的值加上变量 b 的值。

5.[=, &变量名]:按值捕获所有外部变量,但是对于指定的变量按引用捕获。这种方式适用于需要在Lambda表达式内部访问大部分外部变量,但是对于某些变量需要修改它们的值的情况。例如:

int a = 1, b = 2, c = 3;
auto lambda = [=, &c]() { return a + b + (++c); };

在上面的代码中,Lambda表达式按值捕获了变量 a 和 b,并按引用捕获了变量 c,并返回 a+b+c+1。

6.[&, 变量名]:按引用捕获所有外部变量,但是对于指定的变量按值捕获。这种方式适用于需要在Lambda表达式内部修改大部分外部变量,但是对于某些变量只需要访问它们的值的情况。例如:

int a = 1, b = 2, c = 3;
auto lambda = [&, c]() { a += b + c; };

在上面的代码中,Lambda表达式按引用捕获了变量 a 和 b,并按值捕获了变量 c,并将变量 a 的值加上变量 b 和 c 的值。

7.[this]:按值捕获当前对象的指针。这种方式适用于需要在Lambda表达式中访问当前对象的成员变量或者成员函数的情况。例如:

class MyClass {
public:
    void foo() {
        auto lambda = [this]() { return a + b; };
        std::cout << lambda() << std::endl;
    }
private:
    int a = 1, b = 2;
};

在上面的代码中,Lambda表达式按值捕获了当前对象的指针 this,并返回成员变量 a 和 b 的和。

3.仿函数

仿函数是一种重载了函数调用运算符 () 的类或结构体,可以像普通函数一样调用,也可以像普通对象一样存储。因此,仿函数也被称为函数对象。

仿函数可以用于实现自定义的比较器、哈希函数、转换函数等,可以在STL算法和容器中使用,也可以作为回调函数传递给其他函数。

仿函数的定义方式与普通类的定义方式相同,只需要在类中重载 () 运算符即可,例如:

class MyFunctor {
public:
    int operator()(int x, int y) {
        return x + y;
    }
};

在上面的代码中,我们定义了一个名为 MyFunctor 的仿函数,重载了函数调用运算符 (),并实现了将两个整数相加的功能。现在可以像普通函数一样使用 MyFunctor,例如:

MyFunctor f;
int result = f(1, 2); // result = 3

仿函数可以像普通函数一样被传递给其他函数,例如:

void foo(std::function<int(int, int)> func) {
    int result = func(1, 2);
    std::cout << result << std::endl;
}
 
int main() {
    MyFunctor f;
    foo(f);
    return 0;
}

在上面的代码中,我们定义了一个名为 foo 的函数,接受一个类型为 std::function<int(int, int)> 的参数 func,并在函数内部调用 func。在 main 函数中,我们创建了一个 MyFunctor 对象 f,并将其传递给 foo 函数,foo 函数会调用 f 进行计算,并输出结果。

需要注意的是,仿函数与Lambda表达式类似,都可以用于实现可调用对象,但是它们之间的实现方式和使用方式有所不同。如果需要在STL算法和容器中使用可调用对象,建议使用仿函数。

4.函数指针

函数指针是指向函数的指针,它可以像普通指针一样传递、存储和使用。使用函数指针可以实现回调函数、多态等功能。

函数指针的定义方式如下:

返回类型 (*指针变量名)(参数列表);

其中,返回类型 表示函数返回值类型,指针变量名 表示函数指针的变量名,参数列表 表示函数的参数列表。例如:

int add(int a, int b) {
    return a + b;
}
 
int (*p)(int, int) = add;

在上面的代码中,我们定义了一个名为 add 的函数,用于将两个整数相加。然后,我们定义了一个名为 p 的函数指针,指向函数 add。现在,我们可以通过 p 调用函数 add,例如:

int result = p(1, 2); // result = 3

需要注意的是,函数指针的类型与函数的类型是一一对应的,即函数指针的类型必须与函数的类型匹配。在C++中,函数名也可以被解释为指向函数的指针,因此可以直接将函数名赋值给函数指针,例如:

int (*p)(int, int) = &add;
int result = (*p)(1, 2); // result = 3

在上面的代码中,我们使用 & 运算符取出函数 add 的地址,并将其赋值给函数指针 p。然后,我们使用 * 运算符间接调用函数 add,并传递参数 1 和 2,得到结果 3。

函数指针可以用于实现回调函数,例如:

void foo(int (*p)(int, int)) {
    int result = p(1, 2);
    std::cout << result << std::endl;
}
 
int main() {
    foo(add);
    return 0;
}

在上面的代码中,我们定义了一个名为 foo 的函数,接受一个类型为 int (*)(int, int) 的函数指针参数 p,并在函数内部调用 p。在 main 函数中,我们将函数 add 的名称作为参数传递给 foo 函数,foo 函数会调用函数 add 进行计算,并输出结果。

不是必须写成这种形式,可以使用 typedef 或者 using 关键字给函数指针类型取一个别名,从而使代码更加清晰易懂。例如:

typedef int (*FuncPtr)(int, int);
 
void foo(FuncPtr p) {
    int result = p(1, 2);
    std::cout << result << std::endl;
}
 
int add(int a, int b) {
    return a + b;
}
 
int main() {
    FuncPtr p = add;
    foo(p); // 输出 3
    return 0;
}

在上面的代码中,我们使用 typedef 关键字定义了一个名为 FuncPtr 的函数指针类型,它的函数签名为 int (*)(int, int)。然后,我们使用 FuncPtr 作为 foo 函数的参数类型,使用 p 作为 FuncPtr 类型的变量,指向函数 add。最后,我们调用 foo 函数,并将 p 作为参数传递给 foo 函数,foo 函数会调用函数 add 进行计算,并输出结果。

使用 typedef 或者 using 关键字可以使代码更加简洁,同时也使得代码更加易读易懂。

相比于自己定义函数指针,使用 std::function 有以下优点:

更加类型安全:std::function 提供了类型擦除机制,可以存储、复制和调用任意可调用对象(函数、函数指针、仿函数、Lambda表达式等),并在运行时进行类型检查,保证类型安全性。而自己定义的函数指针可能会出现类型不匹配等错误。

更加灵活:std::function 可以存储任意可调用对象,包括函数指针、仿函数、Lambda表达式等,而自己定义的函数指针只能存储函数指针类型的对象。因此,使用 std::function 可以使代码更加灵活、易读、易扩展。

更加易读:使用 std::function 可以使代码更加清晰易懂,因为它可以直接描述函数的返回值类型和参数列表。

更加易用:std::function 提供了函数调用运算符 (),使得可以像调用普通函数一样调用这个函数对象。而自己定义的函数指针需要使用间接调用运算符 * 或 ->,或者使用函数指针名加括号的方式进行调用。

更加通用:std::function 是一个类模板,可以存储任意可调用对象,因此可以用于实现各种应用场景,包括事件处理、状态机、迭代器、排序算法、GUI框架、网络编程等众多应用场景。

因此,相比于自己定义函数指针,使用 std::function 更加安全、灵活、易读、易用、通用。

#include <iostream>
#include <functional>
 
typedef std::function<int(int, int)> FuncType;
 
void foo(FuncType func) {
    int result = func(1, 2);
    std::cout << result << std::endl;
}
 
int add(int a, int b) {
    return a + b;
}
 
int main() {
    FuncType f = add;
    foo(f); // 输出 3
    return 0;
}
  • 32
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值