【学习笔记】QT从入门到实战完整版(Lambda)(2)

Lambda

Lambda 表达式很有意思,相信很多人初次见到 Lambda 表达式都会不能理解有什么用,我也一样,看了视频教程之后,突然意识到,Lambda 真的是太好用了,它可以在某些情况下可以很大程度上简化代码。

应用场景

下面的代码给我的启发:实现通过信号槽的方式实现点击按钮时,触发修改按钮的名字为“停止”。connect 的最后一个参数其实是函数指针,当按钮触发了 clicked 信号时,将会调用该函数指针,那么借助 Lambda 就不需要重新另外定义一个成员函数来作为参数传入。

void Widget::testLambda()
{
    QPushButton *pButton = new QPushButton();

    pButton->setParent(this);
    pButton->setText("启动");

    connect(pButton, &QPushButton::clicked, this, [=](){
        pButton->setText("停止");
    });

}

可以自己写一个函数回调的方式来对比

#include <iostream>

void onEvent()
{
	printf("onEvent\n");
}

void notice(void (*pfunc)())
{
	pfunc();
}

int main(int argc, char *argv[])
{
	notice(onEvent);
	return 0;
}

使用 Lambda 的方式,可以看到代码简化了

#include <iostream>

void notice(void (*pfunc)())
{
	pfunc();
}

int main(int argc, char *argv[])
{
	notice( [](){printf("Lambda2\n");} );
	return 0;
}

介绍说明

从上面的例子可以看到,Lambda 其实本质就是一个函数,只不过这个函数没有名字,所以也叫匿名函数。适合在一些需要传递以函数为参数的表达式中,而函数本事的实现只有几句代码,可以不必另外声明定义函数,直接使用 Lambda 即可。

Lambda 原型说明:

[外部变量访问方式说明符] (参数表) 修饰符 ->返回值类型 {函数体} ()

Lambda 原型解析:

[外部变量访问方式说明符]

用于确定 Lambda 函数体可以用何种方式去访问外部成员

  • [] 中括号里面为空,即表示不访问任何外部成员变量。
  • [&] 以引用方式使用 Lambda 所在作用范围内所有可见的局部变量(包括Lambda所在类的 this)。
  • [=] 以值传递方式使用 Lambda 所在作用范围内所有可见的局部变量(包括Lambda所在类的 this)。
  • [=, &a] 除了a以引用传递方式使用,其他的所有变量以值传递方式使用。
  • [a] 只取a,并且以值传递方式使用。
  • [this] 取 Lambda 所在的类中的 this 指针。如果已经使用了 & 或者 = 就默认添加此选项。

代码示例:


void Widget::showString(char *pString)
{
     qDebug() << pString;
}

void Widget::testLambda()
{
    int a = 1;
    int b = 2;


    // [] 方式
     qDebug() << "[] 方式";
    []()
    {
        // int x = a + b; // 会报错, 因为 a、b 不可见
        qDebug() << "调用中:";
    }();


    // [&] 方式
    qDebug() << "\n[&] 方式";
    qDebug() << "调用前: " << "a:" << a << " b:" << b;
    [&]()
    {
        a = 5;
        b = 3;
        qDebug() << "调用中: "<< "a:" << a << " b:" << b;
        this->showString("[&] 方式使用成员函数 showString()");
    }();
    qDebug() << "调用后: " << "a:" << a << " b:" << b;


    // [=] 方式
    qDebug() << "\n[=] 方式";
    qDebug() << "调用前: " << "a:" << a << " b:" << b;
    [=]()
    {
        // a = 5;  // 会报错, 变量默认为只读属性, 需要使用 mutable 修饰符才可以修改
        qDebug() << "调用中: "<< "a:" << a << " b:" << b;
        this->showString("[] 方式使用成员函数 showString()");
    }();
    qDebug() << "调用后: " << "a:" << a << " b:" << b;


    // [=, &a] 方式
    qDebug() << "\n[=, &a] 方式";
    qDebug() << "调用前: " << "a:" << a << " b:" << b;
    [=, &a]()
    {
        a = 16;
        // b = 7; // 会报错, b变量默认为只读属性, 需要使用 mutable 修饰符才可以修改
        qDebug() << "调用中: "<< "a:" << a << " b:" << b;
        this->showString("[=, &a] 方式使用成员函数 showString()");
    }();
    qDebug() << "调用后: " << "a:" << a << " b:" << b;


    // [a] 方式
    qDebug() << "\n[a] 方式";
    qDebug() << "调用前: " << "a:" << a << " b:" << b;
    [a]()
    {
        //a = 16; // 会报错, a变量默认为只读属性, 需要使用 mutable 修饰符才可以修改
        //qDebug() << "调用中: "<< "a:" << a << " b:" << b; // 会报错, b 变量不可见
        // this->showString("[a] 方式使用成员函数 showString()"); // 会报错, this 不可见
        qDebug() << "调用中: "<< "a:" << a;
    }();
    qDebug() << "调用后: " << "a:" << a << " b:" << b;


    // [this] 方式
    qDebug() << "\n[this] 方式";
    qDebug() << "调用前: " << "a:" << a << " b:" << b;
    [this]()
    {
       // qDebug() << "调用中: "<< "a:" << a << " b:" << b; //会报错 a、b 都不可见
        this->showString("[this] 方式使用成员函数 showString()");
    }();
    qDebug() << "调用后: " << "a:" << a << " b:" << b;

}

运行结果

[] 方式
调用中:

[&] 方式
调用前:  a: 1  b: 2
调用中:  a: 5  b: 3
[&] 方式使用成员函数 showString()
调用后:  a: 5  b: 3

[=] 方式
调用前:  a: 5  b: 3
调用中:  a: 5  b: 3
[] 方式使用成员函数 showString()
调用后:  a: 5  b: 3

[=, &a] 方式
调用前:  a: 5  b: 3
调用中:  a: 16  b: 3
[=, &a] 方式使用成员函数 showString()
调用后:  a: 16  b: 3

[a] 方式
调用前:  a: 16  b: 3
调用中:  a: 16
调用后:  a: 16  b: 3

[this] 方式
调用前:  a: 16  b: 3
[this] 方式使用成员函数 showString()
调用后:  a: 16  b: 3

(参数表)

用于确定 Lambda 传入那些变量作为参数提供给 Lambda 函数体使用,如果没有可以省略 “()” 括号。

#include <iostream>

void onEvent(int type)
{
	printf("onEvent:%d\n", type);
}

void notice(void (*pfunc)(int type), int type)
{
	pfunc(type);
}

int main(int argc, char *argv[])
{
	// 传统的方式, 回传参数
	notice(onEvent, 5);

	// lambda 方式 一 , 回传参数
	notice([](int type) {printf("Lambda :%d\n", type); }, 15);

	// lambda 方式 二 , 回传参数
	auto func = [](int c, int d)
	{
		printf("AutoFunc: c: %d  d:%d\n",c ,d);
	};

	// 调用
	func(8, 9);

	return 0;
}

修饰符

如果没有修饰符则可以省略不写

mutable:

用于修饰 [外部变量访问方式说明符] 中列举的按值传递方式的变量时,可以修改其拷贝值。
相当于 void func(const int a, const int b) 变为 void func(int a, int b)
当有此修饰符时,引入的外部变量不能为空。

void Widget::testLambda()
{
    int a = 1;
    int b = 2;

    // mutable
    qDebug() << "\nmutable";
    qDebug() << "调用前: " << "a:" << a << " b:" << b;
    [a, b] () mutable
    {
        a = 16; // 由于是值传递,a 其实是 lambda 函数体内的局部变量,所以修改的只是 a 的拷贝
        b = 100;
        qDebug() << "调用中: "<< "a:" << a << " b:" << b; // 会报错, b 变量不可见

    }();
    qDebug() << "调用后: " << "a:" << a << " b:" << b;

}
mutable
调用前:  a: 1  b: 2
调用中:  a: 16  b: 100
调用后:  a: 1  b: 2

exception:

说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f()throw(X, Y)

->返回值类型

说明 lambda 函数体可以返回什么样类型的值,实际测试中似乎无论什么情况都可以省略,编译器会自动推测出类型。

#include <iostream>

void notice(int (*pfunc)(int a, int b), int aa, int bb)
{
	printf("notice:%d\n",pfunc(aa, bb));
}

int main(int argc, char *argv[])
{

	// lambda, 回传参数
	notice(
		[](int a, int b) 
		{
			printf("回调隐式返回类型:   a:%d   b:%d  a+b:%d\n", a, b, a + b); 
			return a + b; 
		}, 
		10, 15);

	puts("");

	// lambda, 回传参数
	notice(
		[](int a, int b)->int
		{
			printf("回调显式返回类型:   a:%d   b:%d  a+b:%d\n", a, b, a + b);
			return a + b;
		},
		10, 15);

	puts("");

	// lambda, 显式返回类型
	auto func = [](int c, int d)->int
	{
		printf("显式返回类型: c:%d   d:%d  c+d:%d\n",c ,d, c + d);
		return c + d;
	};

	// 调用
	printf("func:%d\n", func(20, 10));

	puts("");

	// lambda, 隐式返回类型(视频中说只有一处才可以省略,但实际做的实验发现都可以)
	auto func1 = [](int c, int d)
	{
		printf("隐式返回类型: c:%d   d:%d  c+d:%d\n", c, d, c + d);
		if (c > 3)
			return c + d;
		else
			return c;
	};

	// 调用
	printf("func1:%d\n", func1(50, 10));

	return 0;
}

{函数体}

实际具体的实现,一般都只有几句代码。

()

最后这一个括号其实代表的是调用的意思,当需要执行时,加上括号即可。


	// 只是定义, 并未调用
	[]()
	{
		printf("11111111111111\n");
	};

	// 定义了且被调用了
	[]()
	{
		printf("11111111111111\n");
	}();

	// 最简单的 lambda 表达式
	[] {puts("lambda..."); };
	
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
详细目录 1. 序 2. Qt 简介 3. Hello, world! 4. 信号槽 5. 自定义信号槽 6. Qt 模块简介 7. MainWindow 简介 8. 添加动作 9. 资源文件 10. 对象模型 11. 布局管理器 12. 菜单栏、工具栏和状态栏 13. 对话框简介 14. 对话框数据传递 15. 标准对话框 QMessageBox 16. 深入 Qt5 信号槽新语法 17. 文件对话框 18. 事件 19. 事件的接受与忽略 20. event() 21. 事件过滤器 22. 事件总结 23. 自定义事件 24. Qt 绘制系统简介 25. 画刷和画笔 26. 反走样 27. 渐变 28. 坐标系统 29. 绘制设备 30. Graphics View Framework 31. 贪吃蛇游戏(1) 32. 贪吃蛇游戏(2) 33. 贪吃蛇游戏(3) 34. 贪吃蛇游戏(4) 35. 文件 36. 二进制文件读写 37. 文本文件读写 38. 存储容器 39. 遍历容器 40. 隐式数据共享 41. model/view 架构 42. QListWidget、QTreeWidget 和 QTableWidget 43. QStringListModel 44. QFileSystemModel 45. 模型 46. 视图和委托 47. 视图选择 48. QSortFilterProxyModel 49. 自定义只读模型 50. 自定义可编辑模型 51. 布尔表达式树模型 52. 使用拖放 53. 自定义拖放数据 54. 剪贴板 55. 数据库操作 56. 使用模型操作数据库 57. 可视化显示数据库数据 58. 编辑数据库外键 59. 使用流处理 XML 60. 使用 DOM 处理 XML 61. 使用 SAX 处理 XML 62. 保存 XML 63. 使用 QJson 处理 JSON 64. 使用 QJsonDocument 处理 JSON 65. 访问网络(1) 66. 访问网络(2) 67. 访问网络(3) 68. 访问网络(4) 69. 进程 70. 进程间通信 71. 线程简介 72. 线程和事件循环 73. Qt 线程相关类 74. 线程和 QObject 75. 线程总结 76. QML 和 QtQuick 2 77. QML 语法 78. QML 基本元素 79. QML 组件 80. 定位器 81. 元素布局 82. 输入元素 其他文章 宏定义中的 do {…} while (0) C++:在堆上创建对象,还是在栈上?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值