回调函数、Lambda表达式与std::function

文章介绍了C++中std::function的使用,特别是如何通过lambda表达式设置回调函数,展示了其在MysqlManage和MysqlOperate类中的应用,以及lambda表达式的捕获列表和与std::function的配合,强调了std::function的灵活性和通用性。

回调函数

std::function 使得代码更加灵活,允许您传递任何类型的可调用对象,包括函数指针、lambda 表达式和绑定表达式。

MysqlManage.h

#pragma once
#include <functional>
#include <string>
#include <iostream>
#include "MysqlOperate.h"

class MysqlManage
{
public:
    MysqlManage();

private:
    // 使用 std::function 定义回调
    std::function<void(const std::string&)> m_callback;

    MysqlOperate operate;
};

MysqlManage.cpp
 

#include "MysqlManage.h"

MysqlManage::MysqlManage()
{
    // 使用 lambda 表达式设置回调
    m_callback = [this](const std::string& message) {
        std::cout << "Callback received: " << message << std::endl;
    };

    operate.setCallback(m_callback); // 设置回调
    operate.sendMessageTest("Test message"); // 触发测试消息
}

MysqlOperate.h
 

#pragma once
#include <functional>
#include <string>

class MysqlOperate
{
public:
    MysqlOperate();
    // 使用 std::function 来定义回调类型
    void setCallback(const std::function<void(const std::string&)>& callback);
    void sendMessageTest(const std::string& message);

private:
    std::function<void(const std::string&)> m_callback;
    void sendMessage(const std::string& message);
};

MysqlOperate.cpp
 

#include "MysqlOperate.h"

MysqlOperate::MysqlOperate()
{
    
}

void MysqlOperate::setCallback(const std::function<void(const std::string&)>& callback)
{
    m_callback = callback;
}

void MysqlOperate::sendMessage(const std::string& message) {
    if (m_callback) {
        m_callback(message);
    }
}

void MysqlOperate::sendMessageTest(const std::string& message) {
    sendMessage(message);
}

在这个修改版本中,MysqlManage 类使用 lambda 表达式来设置 m_callback,这个 lambda 表达式简单地打印接收到的消息。然后,这个回调被传递给 MysqlOperate 类。当 MysqlOperate 类有消息要发送时,它会调用这个回调函数。

这个方案展示了 std::function 的灵活性,允许您在需要时轻松更改回调逻辑,或者将不同类型的可调用对象作为回调传递。

Lambda

#include <iostream>
#include <functional>
#include <string>

using namespace std;
class MyClass
{
public:
	MyClass();
private:
	int m_int = 0;
};

MyClass::MyClass()
{
	int a = 7;
	std::function<void(const std::string&)> func = [this](const std::string& test) {
		m_int = 10;
		cout << test << endl;
		//a = 8;  捕获不到a
		};

	// 在调用时传递一个字符串参数
	func("Test string");
	std::cout << m_int << std::endl;


	std::function<void(const std::string&)> func2 = [&](const std::string& test) {
		m_int = 12;
		cout << test << endl;
		a = 13;  //因为捕获列表不同,可以捕获到a
		};

	// 在调用时传递一个字符串参数
	func2("Test string2");
	std::cout << m_int << std::endl;
	std::cout << a << std::endl;


	// 定义一个 lambda 表达式,实现加法
	//auto addSum = [](int a, int b) {
	std::function<int(int,int)> addSum = [](int a, int b) {
		return a + b;
		};

	std::cout << addSum(3, 5) << std::endl;
}

int main()
{
    std::cout << "Hello World!\n";
	MyClass m;
}


这段代码展示了在C++中使用lambda表达式的几个不同场景。Lambda表达式是一个可以捕获作用域中变量的匿名函数,它在C++11及其后续版本中被广泛使用。以下是对您代码中lambda表达式使用的详细说明:

MyClass 的构造函数
  1. Lambda表达式作为std::function的实例

    • MyClass构造函数中,首先创建了一个std::function<void(const std::string&)>类型的变量func。这个函数对象可以接受一个std::string类型的参数,并没有返回值。
    • func被初始化为一个lambda表达式,该表达式使用了[this]捕获列表。这意味着lambda可以访问类实例的成员变量m_int
    • 在lambda内部,将m_int设置为10,并打印传入的字符串。
    • 这个lambda不能访问除this以外的局部变量,比如a,因为它没有被捕获。
  2. 不同捕获列表的影响

    • 另一个std::function<void(const std::string&)>类型的变量func2也被定义了,但这次使用了[&]捕获列表,它捕获所有局部变量(包括a)的引用。
    • func2的lambda内部,除了可以修改m_int,还可以修改a的值。这展示了不同捕获方式对lambda访问局部变量的影响。
  3. Lambda作为返回值类型函数

    • 最后,定义了一个std::function<int(int,int)>类型的变量addSum,它是一个接受两个int类型参数并返回int的函数。
    • addSum被初始化为一个执行加法操作的lambda表达式。这个lambda表达式没有使用捕获列表,因为它只使用了它的参数。
主要知识点
  1. Lambda捕获列表

    • [this]:仅捕获类的this指针,可以用来访问类成员。
    • [&]:捕获所有局部变量的引用。
    • []:不捕获任何变量,只能使用传递给lambda的参数。
  2. Lambda与std::function

    • Lambda表达式可以赋值给std::function类型的变量。std::function是一个泛型函数封装器,可以存储、调用几乎任何可调用的目标(函数、lambda表达式、绑定表达式等)。
  3. Lambda使用场景

    • Lambda表达式通常用于简单的操作,如在STL算法中作为函数对象使用,或者作为回调函数。它们提供了一种快速且简洁的方法来定义小型匿名函数。
代码执行流程
  1. 创建MyClass实例。
  2. 构造函数中,首先通过func打印字符串并修改m_int
  3. 然后,通过func2打印另一个字符串,修改m_int并修改局部变量a
  4. 最后,通过addSum执行加法运算并打印结果。
  5. 主函数中打印"Hello World!",然后创建MyClass的实例。

此代码段展示了lambda表达式在C++中的多样性和灵活性,特别是在捕获列表和使用std::function方面。

返回类型自动推断

当 lambda 表达式的主体是一个单一的 return 语句,或者是一个不包含任何 return 语句的单一表达式时,返回类型可以被自动推断。如果 lambda 包含多个 return 语句,并且它们的类型不一致,或者包含更复杂的逻辑,那么必须显式指定返回类型。

以下是两个例子:

auto add = [](int x, int y) {
    return x + y;
};

在这个例子中,由于 lambda 表达式的主体只有一个 return 语句,返回类型(int)可以被自动推断。

auto complex_lambda = [](int x, int y) -> int {
    if (x > y) {
        return x;
    } else {
        return y;
    }
};

在这个例子中,虽然所有的 return 语句都返回 int 类型的值,但由于存在多个 return 语句和更复杂的逻辑,最好显式指定返回类型。当然,在这种特定情况下,由于所有的 return 语句返回相同类型,编译器可能仍然能够推断出返回类型,但显式指定可以提高代码的清晰度。

简单来说,当 lambda 表达式足够简单以便于编译器可以轻松推断其返回类型时,可以省略 -> 和返回类型的显式声明。在更复杂的情况下,显式指定返回类型是一个好的做法。

深入了解 C++ 中的 std::function

什么是 std::function

在C++11中引入的std::function是一个模板类,它在<functional>头文件中定义。std::function的一个关键作用是封装任何可调用的实体,不管它是一个普通函数、一个类的成员函数、一个lambda表达式,还是一个函数对象。

这种灵活性使得std::function在需要回调函数或需要将不同类型的可调用实体以统一的方式处理时非常有用。

为什么使用 std::function
  • 灵活性:可以存储任何类型的可调用实体。
  • 可读性和维护性:提供了一种清晰和一致的方式来处理不同类型的函数和可调用对象。
  • 兼容性:与C++11及更高版本兼容。
std::function 的基本使用

让我们通过一个简单的例子来看看std::function是如何工作的。

示例代码
#include <iostream>
#include <functional>

// 定义一个普通函数
void printNumber(int number) {
    std::cout << "Number: " << number << std::endl;
}

// 定义一个函数对象
struct Print {
    void operator()(int number) const {
        std::cout << "Number: " << number << std::endl;
    }
};

int main() {
    // 将普通函数赋值给 std::function
    std::function<void(int)> f1 = printNumber;
    f1(1); // 输出: Number: 1

    // 将lambda表达式赋值给 std::function
    std::function<void(int)> f2 = [](int number) {
        std::cout << "Number: " << number << std::endl;
    };
    f2(2); // 输出: Number: 2

    // 将函数对象赋值给 std::function
    Print print;
    std::function<void(int)> f3 = print;
    f3(3); // 输出: Number: 3

    return 0;
}

在这个例子中,我们首先定义了一个普通函数printNumber和一个函数对象Print。然后,我们创建了三个std::function实例,分别将普通函数、lambda表达式和函数对象赋值给它们。通过调用这些std::function实例,我们可以看到相同的输出,这展示了std::function如何以统一的方式处理不同类型的可调用实体。

结论

std::function是C++标准库中一个非常强大的组件,它为处理不同类型的可调用实体提供了一种简单、统一的方法。无论您是在处理回调函数、事件处理程序,还是在设计需要通用函数接口的库时,std::function都是一个非常有用的工具。

附录:

函数对象和仿函数

Print结构体重载了operator()。这意味着Print的实例可以像函数一样被调用。这种类型的对象通常被称为函数对象(function object)或仿函数(functor)。

什么是重载operator()

在C++中,重载operator()意味着为类定义一个可以像函数一样被调用的成员函数。这允许类的实例表现得像一个函数。这种特性通常用于创建可以存储状态的可调用对象。

示例解释

在您的示例中,Print结构体定义了如下:

struct Print {
    void operator()(int number) const {
        std::cout << "Number: " << number << std::endl;
    }
};

这里的operator()被定义为接受一个int类型的参数,并打印该数字。由于operator()的存在,您可以创建一个Print的实例,并像调用普通函数一样调用它:

Print print;
print(3); // 使用Print的实例调用operator(),输出: Number: 3

这个特性使得Print实例可以被赋值给std::function<void(int)>类型的变量,因为它符合std::function期望的签名(接受一个int类型的参数,返回void)。

函数对象(Functor)的用途

函数对象在C++中非常有用,特别是在需要传递可调用对象到算法或其他接受函数指针或函数引用的地方时。由于它们可以携带状态(即类的成员变量),所以比普通函数或lambda表达式更灵活。

总的来说,重载operator()使得类的实例可以表现得就像是一个函数,这在C++编程中是一个非常有用的特性。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LanSe___

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

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

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

打赏作者

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

抵扣说明:

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

余额充值