可调用对象function类

函数指针

用于指向普通成员函数和静态成员函数

定义与使用

  • 定义一个普通函数
1
int func(int, int);
  • 定义函数指针类型的变量
1
int (*funcPtr)(int, int); //指针通常用ptr表示

使用函数指针的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>

//定义函数指针类型的变量
int (*funcPtr)(int, int);

//定义一个普通函数
int add(int a, int b){
return a + b;
}

int main()
{
//函数指针指向函数
funcPtr = add; //也可以写funcPtr = &add;函数名等于函数的地址

//调用函数指针指向的函数
funcPtr(1, 2); //不用写成(*funcPtr)(1, 2),编译器会自动解引用,两种写法效果一样

return 0;
}

优点与局限性

优点:

局限性:

  • 不能捕获上下文(如lambda中的闭包)。
  • 语法相对复杂,尤其是指针的声明和使用。

仿函数(Functors)

仿函数,又称为函数对象,是在C++中重载了operator()的==类或结构体实例==,仿函数不仅可以像普通函数一样被调用,还能携带状态。

定义和使用

代码实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>

struct Adder{
int to_add;
Adder(int value) : to_add(value){} //构造函数
int operator()(int x){ //重载()
return x + to_add;
}
};

int main()
{
Adder adder(5);
adder(10); //adder是结构体Adder的实例,且结构体内重载了(),所以可以像函数一样使用
std::cout<<"5+10="<<adder(10)<<std::endl;//输出:5+10=15
return 0;
}

特点

  1. 携带状态:仿函数可以拥有内部状态,通过成员变量存储数据,使其在调用时具备上下文信息。
  2. 灵活性高:可以根据需要添加更多的成员函数和变量,扩展功能。
  3. 性能优化:编译器可以对仿函数进行优化。

仿函数应用场景

1.高级实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>

//可变累加器仿函数
struct Accumulator{
int sum;
Accumulator() : sum(0){}

//重载()
void operator()(int x){
return sum += x;
}
};

int main()
{
Accumulator acc;

acc(10);
acc(20);
acc(30);

std::cout << acc.sum << std::endl; //输出:60

return 0;
}

2.使用仿函数的标准库算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<iostream>
#include<vector>
#include<algorithm>

//反函数:判断一个数是否大于某个阈值
struct IsGreaterThan{
int threshold; //阈值的英文

IsGreaterThan(int t) : threshold(t){}

bool operator()(int x) const {
return x > threshold;
}
};

int main()
{
std::vector<int> numbers = {1, 5, 10, 15, 20};

//使用仿函数进行筛选
IsGreaterThan greaterThan10(10);
auto it = std::find_if(numbers.begin(), numbers.end(), greaterThan10);
if(it != numbers.end()){
std::cout << "第一个大于10的数是:" << *it << std:;endl;
}else{
std::cout << "没有找到大于10的数" << std:;endl;
}

return 0;
}

find_if()是在迭代器指定范围内查找第一个满足自定义条件的元素,前两个参数传入查找范围的迭代器,第三个参数是一元谓词,他是一个可调用对象(函数,Lambda,函数对象等),作用是定义查找查找条件,接受遍历到的元素作为参数,返回bool。

3.仿函数与模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
#include<vector>
#include<algorithm>

//通用比较仿函数
template<typename T>
struct Compare{
bool operator()(const T& a, const T& b){
return a < b;
}
};

int main()
{
std::vector<int> numbers = {5, 2, 8, 1, 9};

//使用仿函数进行排序
std::sort(numbers.begin(), numbers.end(), Compare<int>());

for(auto it : numbers)
{
std::cout << num << " "; //输出:1,2,5,8,9
}

retuurn 0;
}

std::sort主要使用快速排序

4.仿函数的优势

  • 可扩展性:能够根据需要添加更多功能和状态。

  • 与Lambda互补性:在需要携带复杂状态或多次调用时,仿函数比Lambda更适合。

  • 类型安全:仿函数是具体的类型,可以在编译期进行类型检查。

5.何时使用仿函数

  • 需要携带状态时:当回调函数需要维护内部状态时,仿函数是理想选择。

  • 复杂操作:当简单的函数指针或Lambda难以表达复杂逻辑时。

  • 性能关键场景:由于仿函数可以被编译器优化,适用于性能敏感的代码。

Lambda表达式

Lambda表达式是C++11引入的一种轻量级函数对象,允许在代码中定义匿名函数。它们可以捕获周围的变量,具有更强的表达能力。

基本语法

1
2
3
[captures](parameters) -> return_type{
//函数体
}
  • captures:捕获外部变量的方式,可以是值捕获、引用捕获或者混合捕获。

  • parameters:参数列表。

  • return_type:返回类型,可以省略(省略后需把->一起删掉),编译器会自动推导。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include<iostream>
    #include<vector>
    #include<algorithm>

    int main()
    {
    int threshold = 5;
    std::vector<int> numbers = {1,6,3,8,2,7};
    auto new_end = std::remove_if(numbers.begin(), numbers.end(),
    [threshold](int n){//值捕获,把threshold的值捕获使用
    return x < threshold;
    });
    numbers.erase(new_end, numbers.end());
    for(auto& n : numbers){
    std::cout<< n << " "; //输出:6,8,7
    }
    std::cout<<std::endl;

    return 0;
    }

    示例中,remove_if()是把大于等于5的数字移动到容器前,前面小于5的数字会被覆盖掉,

    在C++中, remove_if 是一个标准库算法,用于移除容器中满足特定条件的元素。它不会真正从容器中删除元素,而是将不满足条件的元素移动到容器的前面,并返回一个指向新逻辑末尾的迭代器吗,可以结合 erase 方法来实现真正的删除。

捕获方式

  1. 值捕获[=]):捕获所有外部变量的副本,若要修改需加mutable关键字(修改不会影响原变量)。

  2. 引用捕获[&]):捕获所有外部变量的引用,直接操作原变量,无需mutable关键字即可修改。

  3. 混合捕获:指定部分变量按值捕获,部分按引用捕获,如 [=, &var][&, var]

  4. 无捕获[]):不捕获任何外部变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
#include<memory>

struct Adder{
int to_add;
Adder(int value) : to_add(value){} //构造函数
int operator()(int x){ //重载()
return x + to_add;
}

void add(int x){
to_add += x;
}
};

int main()
{
auto add_ptr = std::make_shared<Adder>(10);
auto lambda1 = [add_ptr](int x){ //lambda1经过这个lambda表达式后就是一个可调用对象
add_ptr->add(x);
//打印引用计数
std::cout << add_ptr.use_count() << std::endl; //2
//这个智能指针被[]通过值的方式捕获,相当于捕获这个智能指针的副本,两个智能指针指向这块内存,引用计数加一
};
lambda1(5);

return 0;
}

可变Lambda

默认情况下,Lambda表达式是不可变的( const )。通过 mutable 关键字,可以允许修改捕获的变量副本。(不常用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>

int main()
{
int count = 0;
auto increment = [count]() mutable{
count++;
std::cout << "Count inside Lambda: " << count << std::endl;
};
increment(); //输出:Count inside Lambda: 1
increment(); //输出:Count inside Lambda: 2

std::cout << "Count outside Lambda: " << count << std::endl;
//输出:Count outside Lambda: 0

return 0;
}

捕获成员函数和类变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<iostream>
#include<vector>
#include<algorithm>

class Processor
{
public:
Processor(int x) : _threshold(x) {}

void process(std::vector<int>& data)
{
std::cout << "处理前的数据:" << std::endl;
for(auto num : data) std::cout << num << " ";
std::cout<<std::endl;

//使用Lambda表达式进行过滤
data.erase(std::remove_if(data.begin(), data.end(), [this](int n){
return n < _threshold;
}), data.end());

std::cout << "处理后的数据:" << std::endl;
for(auto num : data) std::cout << num << " ";
std::cout<<std::endl;
}

private:
int _threshold; //变量前加下划线隐含“私有/内部使用”
};

int main()
{
std::vector<int> numbers = {1, 6, 3, 8, 2, 7};
Processor proc(5);
proc.process(numbers);
/*
输出:
处理前的数据:1 6 3 8 2 7
处理后的数据:6 8 7
*/

return 0;
}

Lambda与标准库算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostrea>
#include<vector>
#include<algorithm>

int main()
{
std::vector<int> numbers = {4, 2, 5, 1, 3}

//使用Lambda表达式进行排序
std::sort(numbers.begin(), numbers.end(),
[](int a, int b) -> bool {
return a < b;
});

std::cout<< "排序后的数字:";
for(auto num : numbers){
std::cout<< num << " "; //输出:1 2 3 4 5
}
std::cout << std::endl

return 0;
}

Lambda表达式的优势

  • 简洁性:代码更加紧凑,易于理解。

  • 灵活性:能够捕获外部变量,适应更多场景。

  • 性能优化:编译器可以对Lambda进行优化,如内联展开。

  • 与标准库的良好集成:与STL算法无缝结合,简化代码逻辑。

std::function 对象

std::function 是C++11提供的一个通用的可调用包装器,能够封装任何可调用对象,包括普通函数、Lambda表达式、函数对象以及绑定表达式。它实现了类型擦除,使得不同类型的可调用对象可以通过统一的接口进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <functional>

// 普通函数
int add(int a, int b) {
return a + b;
}

// 函数对象
struct Multiply {
int operator()(int a, int b) const {
return a * b;
}
};

int main() {
// 封装普通函数
std::function<int(int, int)> func1 = add;
std::cout << "Add: " << func1(3, 4) << std::endl; // 输出:Add: 7

// 封装Lambda表达式
std::function<int(int, int)> func2 = [](int a, int b) -> int {
return a - b;
};
std::cout << "Subtract: " << func2(10, 4) << std::endl; // 输出:Subtract: 6

// 封装函数对象
Multiply multiply;
std::function<int(int, int)> func3 = multiply;
std::cout << "Multiply: " << func3(3, 4) << std::endl; // 输出:Multiply: 12

return 0;
}

特点

  • 类型擦除:可以存储任何符合签名的可调用对象。

  • 灵活性:支持动态改变存储的可调用对象。

  • 性能开销:相比于直接使用函数指针或Lambda, std::function 可能带来一定的性能开销,尤其是在频繁调用时。

用法场景

  • 回调函数的传递。

  • 事件处理系统。

  • 策略模式的实现。

示例:回调机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <functional>

// 定义回调类型
using Callback = std::function<void(int)>;

// 触发事件的函数
void triggerEvent(Callback cb, int value) {
// 事件发生,调用回调
cb(value);
}

int main() {
// 使用Lambda作为回调
triggerEvent([](int x) {
std::cout << "事件触发,值为:" << x << std::endl;
}, 42); // 输出:事件触发,值为:42

// 使用仿函数作为回调
struct Printer {
void operator()(int x) const {
std::cout << "Printer打印值:" << x << std::endl;
}
} printer;

triggerEvent(printer, 100); // 输出:Printer打印值:100

return 0;
}

存储和调用不同类型的可调用对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <functional>
#include <vector>

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

struct Multiply {
int operator()(int a, int b) const {
return a * b;
}
};

int main() {
std::vector<std::function<int(int, int)>> operations;

// 添加不同类型的可调用对象
operations.emplace_back(add); // 普通函数
operations.emplace_back(Multiply()); // 仿函数
operations.emplace_back([](int a, int b) -> int { return a - b; }); // lambda

// 执行所有操作
for(auto& op : operations) {
std::cout << op(10, 5) << " "; // 输出:15 50 5
}
std::cout << std::endl;

return 0;
}

std::bind 操作

std::bind:C++11中提供的一个函数适配器,用于绑定函数或可调用对象的部分参数,生成一个新的可调用对象。它允许提前固定某些参数,简化函数调用或适应接口需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <functional>

// 普通函数
int add(int a, int b) {
return a + b;
}

int main() {
// 绑定第一个参数为10,生成新的函数对象
auto add10 = std::bind(add, 10, std::placeholders::_1);

std::cout << "10 + 5 = " << add10(5) << std::endl; // 输出:10 + 5 = 15

return 0;
}

bind头文件也在<functional>里。

占位符(std::placeholders

std::bind 使用占位符来表示未绑定的参数,这些占位符决定了在生成的新函数对象中如何传递参数。

常用占位符包括:

  • std::placeholder::_1
  • std::placeholder::_2
  • std::placeholder::_3

等等,最多10个。

  • 以 _1 、 _2 、 _3 …表示,代表新可调用对象被调用时的第1、2、3…个参数。

  • 属于 std::placeholders 命名空间,使用时需显式指定(如 std::placeholders::_1 )。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #include <iostream>
    #include <functional>

    void display(const std::string& msg, int count) {
    for(int i = 0; i < count; ++i) {
    std::cout << msg << std::endl;
    }
    }

    int main() {
    // 绑定消息为"Hello",生成新的函数对象,只需要传递次数
    auto sayHello = std::bind(display, "Hello", std::placeholders::_1);

    sayHello(3);
    /*
    输出:
    Hello
    Hello
    Hello
    */

    // 绑定次数为2,生成新的函数对象,只需要传递消息
    auto sayTwice = std::bind(display, std::placeholders::_1, 2);
    sayTwice("Hi");
    /*
    输出:
    Hi
    Hi
    */

    return 0;
    }

与Lambda表达式的对比

std::bind 曾在C++11中广泛使用,但随着Lambda表达式的普及,很多情况下Lambda更为直观和高效。不过,在某些复杂的参数绑定场景下, std::bind 依然有其独特优势。

使用std::bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <functional>

int multiply(int a, int b) {
return a * b;
}

int main() {
// 绑定第一个参数为2,生成新的函数对象
auto multiplyBy2 = std::bind(multiply, 2, std::placeholders::_1);

std::cout << "2 * 5 = " << multiplyBy2(5) << std::endl; // 输出:2 * 5 = 10

return 0;
}

使用Lambda表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <functional>

int multiply(int a, int b) {
return a * b;
}

int main() {
// 使用Lambda表达式绑定第一个参数为2
auto multiplyBy2 = [](int b) -> int {
return multiply(2, b);
};

std::cout << "2 * 5 = " << multiplyBy2(5) << std::endl; // 输出:2 * 5 = 10

return 0;
}

使用std::bind绑定成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <functional>

class Calculator {
public:
int multiply(int a, int b) const {
return a * b;
}
};

int main() {
Calculator calc;

// 绑定成员函数multiply,固定第一个参数为5
auto multiplyBy5 = std::bind(&Calculator::multiply, &calc, 5, std::placeholders::_1);

std::cout << "5 * 3 = " << multiplyBy5(3) << std::endl; // 输出:5 * 3 = 15

return 0;
}

类的成员函数需要加取地址符(&

使用Lambda表达式绑定成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <functional>

class Greeter {
public:
void greet(const std::string& name) const {
std::cout << "Hello, " << name << "!" << std::endl;
}
};

int main() {
Greeter greeter;

// 使用Lambda表达式绑定成员函数
auto greetFunc = [&greeter](const std::string& name) {
greeter.greet(name);
};

greetFunc("Alice"); // 输出:Hello, Alice!

return 0;
}

如果绑定的成员函数有返回值在Lambda表达式中要return

绑定静态成员函数

静态成员函数不依赖于类的实例,可以像普通函数一样使用 std::bindstd::function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <functional>

class Logger {
public:
static void log(const std::string& message) {
std::cout << "Log: " << message << std::endl;
}
};

int main() {
// 使用std::bind绑定静态成员函数
auto logFunc = std::bind(&Logger::log, std::placeholders::_1);

logFunc("This is a static log message."); // 输出:Log: This is a static log message.

return 0;
}

绑定带有返回值的成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <functional>

class Math {
public:
double power(double base, double exponent) const {
double result = 1.0;
for(int i = 0; i < static_cast<int>(exponent); ++i) {
result *= base;
}
return result;
}
};

int main() {
Math mathObj;

// 绑定成员函数power,固定基数为2
auto powerOf2 = std::bind(&Math::power, &mathObj, 2.0, std::placeholders::_1);

std::cout << "2^3 = " << powerOf2(3) << std::endl; // 输出:2^3 = 8

return 0;
}

注意事项

  1. 对象生命周期:绑定成员函数时,确保对象在可调用对象使用期间依然存在,以避免悬空指针问题。

  2. 指针与引用:可以通过指针或引用传递对象实例给 std::bindLambda表达式。

  3. 捕获方式:在使用Lambda表达式时,选择合适的捕获方式(值捕获或引用捕获)以确保对象的正确访问。