Files
scuc-qt-course/tools/wiki_data/bodies/58954527.json
T
张宗平 13b3ebbe14 课程代码仓库初始化:Part1 (144) + Part3 (29) 全量生成与验证
把川大 wiki「02.Qt方向」课程代码整理为 CMake 管理的可编译运行项目。

Part1 C/C++ 强化 (p01/, 144 目标, host gcc/g++ 构建, 全绿):
- 修复全部编译失败:补 <cstring>/<fstream>/<iomanip>/<vector> 等缺失头
  (common_fix + infer_stl_includes 自动检测注入)、修正 pause() 壳注入
  (改检测已转换后的 pause 调用)、namespace 提升出函数体、const 成员函数
  正确性、dedupe 不再破坏函数重载集、K&R 隐式 int / 动态异常规范等
  C++17 移除特性的可编译等价改写。
- 目录采用深缩写 p01/ch04/s03/ (原 part1_cpp/ch04_class_object/03_ctor_dtor)。

Part3 Qt 桌面 (p03/, 29 目标, 隔离 Qt 5.14.2 构建, 全绿):
- gen_part3.py: Qt 外壳模板 (main+QApplication+show, QTimer 自动退出便于
  offscreen 验证)、AUTOMOC + .moc include、rewrite_* 处理散文混入/重载/
  资源依赖、生成占位 png/gif + .qrc + rundata 样例数据。
- 修正 QLable 笔误、全角分号、.→->、Windows 路径、QApplication 阻塞事件
  循环改为 QCoreApplication 等。

任务1 概念/版本验证:
- tools/std_probe.py: 跨 -std= 编译探针 (--pedantic-errors 让已废除特性硬失败)。
- tools/demo_versions/: K&R 隐式 int、三目左值、void* 转换、enum 赋整、
  const 经指针、动态异常规范 共 6 个跨版本演示。
- docs/VERSION_NOTES.md: 每条「原文说法→C17/C++17 是否成立→何版本成立→已验证」。

文档: 顶层 README、docs/SOURCE_PAGES.md (页面→目录映射)、
docs/ERRATA.md (Part1 修正)、docs/ERRATA_part3.md (Part3 修正)。
另修复 tools/run.sh / run_qt.sh 的 ${1:?...} 引号语法 bug。

隔离 Qt 验证: ldd 0 系统 Qt 引用; 173/173 目标全绿; p03 offscreen 运行通过。
2026-06-30 14:16:55 +08:00

1 line
16 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"id":"58954527","type":"page","status":"current","title":"4 信号和槽机制","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:25.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954527/version/1"},"_expandable":{"content":"/rest/api/content/58954527"}},"body":{"storage":{"value":"<p>信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。<span style=\"color: rgb(255,0,0);\"><strong>当某个事件发生之后</strong></span>,比如,按钮检测到自己被点击了一下,<span style=\"color: rgb(255,0,0);\"><strong>它就会发出一个信号(signal</strong></span>。这种发出是没有目的的,类似广播。<span style=\"color: rgb(255,0,0);\"><strong>如果有对象对这个信号感兴趣,它就会使用连接(connect)函数</strong></span>,意思是,<span style=\"color: rgb(255,0,0);\"><strong>将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号</strong></span>。也就是说,<span style=\"color: rgb(255,0,0);\"><strong>当信号发出时,被连接的槽函数会自动被回调</strong></span>。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。</p><h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"ec643bf8-2ee6-4b63-a766-78633fa2eedf\"><ac:parameter ac:name=\"\">_Toc471841999</ac:parameter></ac:structured-macro>4.1 系统自带的信号和槽</h1><p>下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?<br />其实无法两行代码就可以搞定了,我们看下面的代码</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"33311082-612f-4671-bd9b-51834259eb92\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[ QPushButton * quitBtn = new QPushButton(\"关闭窗口\",this);\n connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);\n\n]]></ac:plain-text-body></ac:structured-macro><p><br class=\"atl-forced-newline\" />第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式<br />connect()函数最常用的一般形式:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"1a77537f-8210-4f9d-9c96-4e3455e91f5f\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[connect(sender, signal, receiver, slot);]]></ac:plain-text-body></ac:structured-macro><p><br />参数解释:</p><ul><li style=\"list-style-type: none;\"><ul><li>sender:发出信号的对象</li><li>signal:发送对象发出的信号</li><li>receiver:接收信号的对象</li><li>slot:接收对象在接收到信号之后所需要调用的函数(槽函数)</li></ul></li></ul><p>那么系统自带的信号和槽通常如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类<ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"0a7d65c1-2e5c-4645-b397-1ae5d7016ccc\"><ac:parameter ac:name=\"\">sidebar-content</ac:parameter></ac:structured-macro>QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个<br /><ac:image ac:height=\"249\" ac:width=\"329\"><ri:attachment ri:filename=\"worddav4d6de52b14ceef805cbf2402eaa6205f.png\" /></ac:image><br />这里的clicked就是我们要找到,槽函数的寻找方式和信号一样,只不过他的关键字是slot。</p><h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"b7185fc4-c3e4-46dd-b0f5-c1740016d830\"><ac:parameter ac:name=\"\">_Toc471842000</ac:parameter></ac:structured-macro>4.2 自定义信号和槽</h1><p>使用connect()可以让我们连接系统提供的信号和槽。但是,Qt 的信号槽机制并不仅仅是使用系统提供的那部分,还会允许我们自己设计自己的信号和槽。<br />下面我们看看使用 Qt 的信号槽:<br /><br /></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"285a0674-ed0c-42ad-9b65-8ef26d4be60e\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[首先定义一个学生类和老师类:\n\t老师类中声明信号 饿了 hungry\nsignals:\n void hungury();\n\t学生类中声明槽 请客 treat\n\tpublic slots:\n void treat();\n\t在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客\n\tvoid MyWidget::ClassIsOver()\n{\n //发送信号\n emit teacher->hungury();\n}\n\t学生响应了槽函数,并且打印信息\n//自定义槽函数 实现\nvoid Student::eat()\n{\n qDebug() << \"该吃饭了!\";\n}\n在窗口中连接信号槽\n teacher = new Teacher(this);\n student = new Student(this);\n\n connect(teacher,&Teacher::hungury,student,&Student::treat);\n并且调用下课函数,测试打印出 “该吃饭了”\n\n]]></ac:plain-text-body></ac:structured-macro><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"1022e920-084d-49e3-8729-893a3ba9ddc8\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[\n自定义的信号 hungry带参数,需要提供重载的自定义信号和 自定义槽\nvoid hungury(QString name); 自定义信号\nvoid treat(QString name ); 自定义槽\n但是由于有两个重名的自定义信号和自定义的槽,直接连接会报错,所以需要利用函数指针来指向函数地址, 然后在做连接\nvoid (Teacher:: * teacherSingal)(QString) = &Teacher::hungury;\nvoid (Student:: * studentSlot)(QString) = &Student::treat;\nconnect(teacher,teacherSingal,student,studentSlot);]]></ac:plain-text-body></ac:structured-macro><p><br class=\"atl-forced-newline\" /><br class=\"atl-forced-newline\" /><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"f95d91bc-7a01-466d-aaec-cdb8e3a09228\"><ac:parameter ac:name=\"\">_Toc466556769</ac:parameter></ac:structured-macro><strong>自定义信号槽需要注意的事项:</strong></p><ul><li><strong>发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);</strong></li><li><strong>信号和槽函数返回值是 void</strong></li><li><strong>信号只需要声明,不需要实现</strong></li><li><strong>槽函数需要声明也需要实现</strong></li><li><strong>槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;</strong></li><li><strong>使用 emit 在恰当的位置发送信号;</strong></li><li><strong>使用connect()函数连接信号和槽。</strong></li><li><strong>任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数</strong></li><li><strong>信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。</strong></li><li><strong>如果信号和槽的参数不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少)。</strong></li></ul><h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"80e6c172-3a36-42f9-a3f8-92433b5d6baf\"><ac:parameter ac:name=\"\">_Toc466556770</ac:parameter></ac:structured-macro><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"38894c36-ce7d-4990-8ca3-0a72aa70ef82\"><ac:parameter ac:name=\"\">_Toc471842001</ac:parameter></ac:structured-macro>4.3信号槽的拓展</h1><ul><li>一个信号可以和多个槽相连</li></ul><p><strong>如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。</strong></p><ul><li>多个信号可以连接到一个槽</li></ul><p><strong>只要任意一个信号发出,这个槽就会被调用</strong>。</p><ul><li>一个信号可以连接到另外的一个信号</li></ul><p><strong>当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。</strong></p><ul><li>槽可以被取消链接</li></ul><p>这种情况并不经常出现,因为<strong>当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽</strong>。</p><ul><li>信号槽可以断开</li></ul><p>利用<strong>disconnect</strong>关键字是可以断开信号槽的</p><ul><li>使用Lambda 表达式</li></ul><p>在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。<br />在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理。后面我们会详细介绍什么是Lambda表达式</p><h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"e8aa4e4a-f40f-4882-9e40-3c8ad420d528\"><ac:parameter ac:name=\"\">_Toc471842002</ac:parameter></ac:structured-macro>4.4 Qt4版本的信号槽写法</h1><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"a3b47f15-2316-4803-ab5c-69b28429a0ae\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[connect(zt,SIGNAL(hungry(QString)),st,SLOT(treat(QString)));]]></ac:plain-text-body></ac:structured-macro><p><br />这里使用了<span style=\"color: rgb(255,0,0);\"><strong>SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串</strong></span>。注意到connect()函数的 signal 和 slot 都是接受字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。<br />Qt5在语法上完全兼容Qt4,而反之是不可以的。</p><h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"27350a82-1d01-4627-a632-27dda7173129\"><ac:parameter ac:name=\"\">_Toc471842003</ac:parameter></ac:structured-macro>4.5 Lambda表达式</h1><p>C++11<span>中的</span>Lambda<span>表达式<strong>用于定义并创建匿名的函数对象</strong>,以简化编程工作。首先看一下</span>Lambda<span>表达式的基本构成:</span></p><p><br /></p><p><br /></p><p><br /></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"e569f479-8b09-413c-8396-6bf42eca2455\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[[capture](parameters) mutable ->return-type\n{\nstatement\n}\n\n]]></ac:plain-text-body></ac:structured-macro><p><br /></p><p><br /></p><p><br /></p><p>[<span>函数对象参数</span>](<span>操作符重载函数参数</span>)mutable -&gt;<span>返回值</span>{<span>函数体</span>}</p><p>① <span>函数对象参数;</span>[]<span>,标识一个</span><strong>Lambda<span>的开始</span></strong><span>,这部分必须存在,<strong>不能省略</strong>。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义</span>Lambda<span>为止时</span>Lambda<span>所在作用范围内可见的局部变量(包括</span>Lambda<span>所在类的</span>this<span>)。函数对象参数有以下形式:</span></p><ul><li>空。没有使用任何函数对象参数。</li><li>=。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是<strong>值传递方式</strong>(相当于编译器自动为我们按值传递了所有局部变量)。</li><li>&amp;。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是<strong>引用传递方式</strong>(相当于编译器自动为我们按引用传递了所有局部变量)。</li><li>this。函数体内可以使用Lambda所在类中的成员变量。</li><li>a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。<strong>要修改传递进来的a的拷贝</strong>,可以添加mutable修饰符。</li><li>&amp;a。将a按引用进行传递。</li><li>a, &amp;b。将a按值进行传递,b按引用进行传递。</li><li>=&amp;a, &amp;b。除a和b按引用进行传递外,其他参数都按值进行传递。</li><li>&amp;, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。</li></ul><p>② 操作符重载函数参数;</p><p>标识重载的()<span>操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:</span>(a,b)<span>)和按引用(如:</span>(&amp;a,&amp;b)<span>)两种方式进行传递。</span></p><p>③ 可修改标示符;</p><p>mutable<span>声明,这部分可以省略。按值传递函数对象参数时,加上</span>mutable<span>修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。</span></p><p><br /></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"42152b3f-c382-4135-8abc-c491d9b06960\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[ QPushButton * myBtn = new QPushButton (this);\n QPushButton * myBtn2 = new QPushButton (this);\n myBtn2->move(100,100);\n int m = 10;\n\n connect(myBtn,&QPushButton::clicked,this,[m] ()mutable { m = 20; qDebug() << m; });\n\n connect(myBtn2,&QPushButton::clicked,this,[=] () { qDebug() << m; });\n\n qDebug() << m;\n\n]]></ac:plain-text-body></ac:structured-macro><p><br /></p><p>④ 函数返回值;</p><p>-&gt;<span>返回值类型,标识函数返回值的类型,当返回值为</span>void<span>,或者函数体中只有一处</span>return<span>的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。</span></p><p>⑤ 是函数体;</p><p>      {}<span>,标识函数的实现,这部分不能省略,但函数体可以为空。</span></p><p><br /></p>","representation":"storage","_expandable":{"content":"/rest/api/content/58954527"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":23},"_links":{"webui":"/pages/viewpage.action?pageId=58954527","edit":"/pages/resumedraft.action?draftId=58954527","tinyui":"/x/H5ODAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954527"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954527/child","restrictions":"/rest/api/content/58954527/restriction/byOperation","history":"/rest/api/content/58954527/history","ancestors":"","descendants":"/rest/api/content/58954527/descendant","space":"/rest/api/space/2CT"}}