Qt入门日记1
目录
1.Qt简介和案例
2.第一个Qt程序
3.学会查看帮助文档
4.创建一个按钮
5.对象树简介
6.Qt的坐标系
7. 信号和槽
7.1自定义信号和槽
7.2信号连接信号
7.3拓展
7.4Qt4版本以前的connect
1.Qt简介和案例
Qt是一个跨平台的C++图形用户界面应用程序框架(就是一个库吧),它是完全面向对象的,允许真正意义上的组件式编程。
Qt是Linux桌面环境KDE的基础,它有商业版和开源版。
Qt的成功案例有很多,例如Linux桌面环境KDE、WPS等等。
它的优点如下:
- 跨平台,几乎支持所有平台
- 接口简单
- 一定程度上简化了内存回收机制(对象树)
- 可以进行嵌入式开发
2.第一个Qt程序
在创建项目时会提示选择三个基类当中的某一个:
这三个基类之间的关系如下:
QMainWindow和QDialog继承自QWidget:
- QWidget是一个空白窗口
- QMainWindow是一个带有菜单栏、工具栏、状态栏等等的一个窗口
- QDialog是一个提示对话框
项目创建完成后会有一个".pro"文件,这个文件时当前项目的工程文件,里面有一些很重要的内容,不要不要随意修改".pro"文件的内容甚至不要加注释。
可以看到默认情况下,Qt包含了core模块和gui模块,如果是4版本以上的话还会添加一个widgets模块。原因在于4版本以前widgets模块属于gui模块,4版本以后将widgets独立出来了。
当前项目自动构建出了一些源文件和头文件,可以很轻松的找到主函数:
其中,应用程序对象有且仅有一个, Widget类继承自QWidget,调用该类的show方法可以单独显示一个窗口:
注意Widget类定义当中的"Q_OBJECT",这是一个宏,它的作用是让该类支持信号和槽机制。还要注意其构造函数,有一个名为parent指向QWidget类的指针,这与对象树有关,稍后做解释。
3.学会查看帮助文档
学习任何技术都需要有帮助文档,Qt提供了这样的帮助文档。选中关键字并且按下"F1"可以弹出帮助文档:
还有一种方法是直接打开帮助文档的独立引用程序,它的位置在"Qt安装路径/版本号/mingw_64/bin"下:
4.创建一个按钮
Qt当中的按钮类为"QPushButton",通过帮助文档可以查看的到:
接下来在代码当中创建一个按钮并让其显示:
#include "widget.h"#include <QApplication>
#include <QPushButton> // 包含头文件
int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;QPushButton *btn = new QPushButton;btn->show();// 显示w.show();return a.exec();
}
可以看到如果使用QPushButton类的show方法,是单独打开一个窗口并显示。很显然这并不是想要的效果。如果想要依附于已存在的窗口显示,只需要为按钮指定一个父亲即可:
#include "widget.h"#include <QApplication>
#include <QPushButton> // 包含头文件
int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;QPushButton *btn = new QPushButton;btn->setParent(&w);// 依附于已经存在的窗口//btn->show();// 显示w.show();return a.exec();
}
此时按钮便依附于已存在的窗口了。如果想要设置一下按钮的大小、显示文字、改变位置,可以使用QPushButton类的某些成员函数:
#include "widget.h"#include <QApplication>
#include <QPushButton> // 包含头文件
int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;QPushButton *btn = new QPushButton;btn->setParent(&w);// 依附于已经存在的窗口btn->setText("First Button");// 显示文字btn->resize(200,200);// 设置一下按钮的大小btn->move(200,100);// 移动到指定为止//btn->show();// 显示w.resize(600,400);// 固定一下窗口的大小w.show();return a.exec();
}
5.对象树简介
在没有接触Qt之前,上面的代码对于C++程序员来说是难受的,因为new了一个QPushButton对象却并没有做delete操作。
事实上在Qt当中有些情况下并不需要做delete操作,这就是对象树的作用。
对象树是Qt当中的一种自动回收内存的机制,它可以简化程序员的工作。就如上面的代码,程序员可以不用担心内存泄漏,即不用delete。
想要不delete堆上的内存空间,必须符合两个条件:
- new出来的对象必须是QObject的亲缘类对象
- 该对象必须指定一个QObject的亲缘类对象
QObject是最顶层的基类,基本上Qt当中的内置类都继承自于QObect。因此比如QWidget、QMainWindow、QDialog、QPushButton等等,它们的最顶层的基类就是QObject。
当new出来的对象指定了父亲之后,就相当于加入了对象树。这颗对象树的任意节点(对象)析构时,会自动释放其所有子孙节点(对象)。
此时可以做一个实验,即自定义一个类,取名为"MyTest"并继承自QPushButton类,指定它的父亲为Widget类:
#ifndef MYTEST_H
#define MYTEST_H#include <QWidget>
class MyTest : public QWidget
{Q_OBJECT
public:explicit MyTest(QWidget *parent = nullptr);~MyTest();
signals:};#endif // MYTEST_H
#include "mytest.h"
#include <QDebug>
MyTest::MyTest(QWidget *parent): QWidget{parent}
{}MyTest::~MyTest()
{qDebug() << "~MyPushButton()";
}
修改一下Wdiget类的析构函数:
#include "widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent): QWidget(parent)
{
}Widget::~Widget()
{qDebug() << "~Widget()";
}
然后在主函数当中new出来MyPushButton类,指定其父亲为Wdiget,运行后关闭窗口观察打印结果:
#include "widget.h"#include <QApplication>
#include <QPushButton> // 包含头文件
#include "mytest.h"
int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;MyTest *mbtn = new MyTest(&w);w.resize(600,400);w.show();return a.exec();
}
关闭窗口后的输出结果,可以看到mbtn所指向的堆空间并没有手动delete,但是由于对象树的存在,不会造成内存泄漏。
这里可以用一幅图来解释:
此时可能会有个问题,即刚才说的是对象树当中的某个对象释放时会先析构其所有子孙节点。那么为什么先打印父对象的析构,后打印子孙对象的析构呢?这个原因在于析构函数分成两部分执行,一是执行析构函数函数体内的代码,二是执行函数体之后的清理工作。函数体内的代码是释放当前对象的大部分资源,例如对象内部new出来的空间;函数体之后是清理对象本身。所以对象树可以理解成清理对象本身之前先析构其所有子孙对象。
6.Qt的坐标系
Qt的窗口是有坐标系的,上面在控制按钮的位置时涉及到坐标系。Qt窗口的坐标系如下图所示:
7. 信号和槽
其实信号和槽可以理解为"信号和信号处理"。熟悉Linux系统编程的话,理解信号和槽不会太难。
信号和槽涉及到两个角色,即信号的发送方和信号的接收方;涉及到两个动作,即信号的发送和信号的处理。
总体而言可以分成两个部分:
connect是一个函数,作用类似于Linux当中的signal()。它的作用我个人理解成"注册一个信号产生时需要做的动作"。
通过帮助文档可以查看该函数:
原型可以是这样的:
connect(信号的发送发,要发送的信号,信号的接收方,接收到信号后要做的动作)
做一个小任务,即创建一个按钮,点击该按钮后关闭窗口:
#include "widget.h"#include <QApplication>
#include <QPushButton> // 包含头文件#include "mytest.h"
int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;QPushButton *btn = new QPushButton(&w);btn->setText("PushButton");btn->move(200,100);btn->resize(100,100);QObject::connect(btn,&QPushButton::clicked,&w,&Widget::close);// 注册信号w.show();return a.exec();
}
该按钮点击后,窗口就关闭了。
综上,信号和槽的优点非常明显:
- 使得编程多样化,可以实现更多丰富的功能,非常自由
- 低耦合,信号的发送方和接收方是低耦合的,这样设计有利于代码维护,出错的概率降低
7.1自定义信号和槽
如果自定义了一些类,并且想让这些类也能够使用信号和槽机制,Qt是支持的。
创建一个Student类和Teacher类,signals关键字下写自定义信号函数,该函数只声明不实现;public slots关键字(较高版本的Qt可以写到public下和全局下)下写自定义槽函数,该函数要声明和定义。
当前的任务是Teacher发出一个"下课"信号,学生做出"走出教室"动作。
#ifndef TEACHER_H
#define TEACHER_H#include <QObject>class Teacher : public QObject
{Q_OBJECT
public:explicit Teacher(QObject *parent = nullptr);signals:void ClassOver();// 下课,只声明不实现
};#endif // TEACHER_H
#ifndef STUDENT_H
#define STUDENT_H#include <QObject>class Student : public QObject
{Q_OBJECT
public:explicit Student(QObject *parent = nullptr);void GoOutOfClass();// 要声明和实现
signals:};#endif // STUDENT_H
#include "student.h"
#include <QDebug>
Student::Student(QObject *parent): QObject{parent}
{}
void Student::GoOutOfClass()
{qDebug() << "学生走出教室!";
}
此时在主函数当中完成任务:
int main(int argc, char *argv[])
{QApplication a(argc, argv);Student *stu = new Student;Teacher *tec = new Teacher;QObject::connect(tec,&Teacher::ClassOver,stu,&Student::GoOutOfClass);emit tec->ClassOver();// emit关键字,发送信号delete stu;delete tec;return a.exec();
}
此时要注意一个关键字,即"emit",它的作用在于触发信号。
其他方面,无论是信号函数还是槽函数,都是可以带参数的。
这里再做一个实验,即让信号函数之间发生重载,槽函数之间发生重载:
#ifndef TEACHER_H
#define TEACHER_H#include <QObject>
#include <QString>
class Teacher : public QObject
{Q_OBJECT
public:explicit Teacher(QObject *parent = nullptr);signals:void ClassOver();// 下课,只声明不实现void ClassOver(QString str);
};#endif // TEACHER_H
#ifndef STUDENT_H
#define STUDENT_H#include <QObject>
#include <QString>
class Student : public QObject
{Q_OBJECT
public:explicit Student(QObject *parent = nullptr);void GoOutOfClass();// 要声明和实现void GoOutOfClass(QString str);
signals:};#endif // STUDENT_H
#include "student.h"
#include <QDebug>
Student::Student(QObject *parent): QObject{parent}
{}
void Student::GoOutOfClass()
{qDebug() << "学生走出教室!";
}void Student::GoOutOfClass(QString str)
{qDebug() << "老师说:" << str;
}
那么在主函数当中修改一下代码:
int main(int argc, char *argv[])
{QApplication a(argc, argv);Student *stu = new Student;Teacher *tec = new Teacher;QObject::connect(tec,&Teacher::ClassOver,stu,&Student::GoOutOfClass);emit tec->ClassOver("下课啦!");// emit关键字,发送信号delete stu;delete tec;return a.exec();
}
此时出现了编译报错!原因在于connect函数当中的参数,信号函数和槽函数根本没有明确唯一指向,导致编译器报错。解决方法是利用函数指针来明确唯一指向:
int main(int argc, char *argv[])
{QApplication a(argc, argv);Student *stu = new Student;Teacher *tec = new Teacher;void (Teacher::*tecSignal)(QString) = &Teacher::ClassOver;void (Student::*stuSlot)(QString) = &Student::GoOutOfClass;QObject::connect(tec,tecSignal,stu,stuSlot);emit tec->ClassOver("下课啦!");// emit关键字,发送信号delete stu;delete tec;return a.exec();
}
需要注意,在声明和定义槽函数时,它们的返回值都为void。
如果需要取消注册的话,可以使用disconnect函数,这里就不做示例了。
7.2信号连接信号
信号不一定非要与槽函数connect在一起,信号和信号之间也可以连接。
现在实现一个需求,即创建一个按钮,按钮点击后老师下课,学生走出教室。此时代码可以修改为:
int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;Student *stu = new Student(&w);Teacher *tec = new Teacher(&w);void (Teacher::*tecSignal)() = &Teacher::ClassOver;void (Student::*stuSlot)() = &Student::GoOutOfClass;QObject::connect(tec,tecSignal,stu,stuSlot);QPushButton *btn = new QPushButton(&w);btn->setText("PushButton");btn->resize(200,100);QObject::connect(btn,&QPushButton::clicked,tec,tecSignal);w.resize(600,400);w.show();return a.exec();
}
此时点击按钮会让老师产生下课信号,随后学生走出教室。
但是注意这里有个非常局限的东西,那就是信号连接信号时,作为槽函数的信号函数无法传递参数!这个时候就要配合全局函数或者lambda表达式,即在它们内部触发信号!
7.3拓展
下面的规则是总结和拓展:
- 一个信号可以连接多个槽函数
- 多个信号可以连接同一个槽函数
- 信号和槽函数的参数类型必须一一对应
- 信号函数的参数个数可以多于槽函数的参数个数
对于规则3和4的解释为,槽函数和可以选择接收和不接受信号传递过来的参数,如果选择接收了信号的参数,那么类型必须一致。以一幅图来理解:
7.4Qt4版本以前的connect
上面介绍的都是Qt4版本之后的connect函数用法,在Qt4版本之前,写法是下面这样的:
QObject::connect(tec,SIGNAL(&Teacher::ClassOver(QString)),stu,SLOT(&Student::GoOutOfClass(QString)));
显然这样的方式是非常直观的,因为可以免除函数指针的繁琐写法。但是这种写法必然存在恶劣的缺点否则较新的Qt版本也不会出现较新的写法。
根本原因在于SIGNAL和SLOT关键字会将括号内容字符串化,并且这些字符串之间还不会做任何的比较,所以导致编译器无法报错,导致后续维护项目困难。
相关文章:

Qt入门日记1
目录 1.Qt简介和案例 2.第一个Qt程序 3.学会查看帮助文档 4.创建一个按钮 5.对象树简介 6.Qt的坐标系 7. 信号和槽 7.1自定义信号和槽 7.2信号连接信号 7.3拓展 7.4Qt4版本以前的connect 1.Qt简介和案例 Qt是一个跨平台的C图形用户界面应用程序框架(就是一个库吧…...

SpringBoot_第七章(读写分离)
这里列举了三种读写分离实现方案,分别是如下三种 1:MybatisPlus(读写分离) 1.1:首先创建三个数据库1主2从 表名是user表 1.2:代码实例 1:导入pom <!--MybatisPlus的jar 3.0基于jdk8--><depend…...

linux下mysql-8.2.0集群部署(python版本要在2.7以上)
目录 一、三台主机准备工作 1、mysql官方下载地址:https://dev.mysql.com/downloads/ 2、修改/etc/hosts 3、关闭防火墙 二、三台主机安装mysql-8.2.0 1、解压 2、下载相应配置 3、初始化mysql,启动myslq,设置开机自启 4、查看初始密…...

40 深度学习(四):卷积神经网络|深度可分离卷积|colab和kaggle的基础使用
文章目录 卷积神经网络为什么要卷积卷积的具体流程池化tensorflow代码 深度可分离卷积原理介绍计算量对比代码参数计算例子 colab 和 kagglecolabkaggle如何在colab上使用kaggle的数据 卷积神经网络 卷积神经网络的基本结构 1: (卷积层(可选)池化层) * N全连接层 *…...
Spring Boot面向切面加注解
一.项目pom.xml文件引入切面依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>二.定义注解类 import java.lang.annotation.*;/*** desc 错误日志注解* au…...
uniapp小程序授权统一处理
1.使用 1.将工具代码引入到utils中 const authorize (scope, isOne false, isMust false) > {if (!scope || !authorizeObj[scope]) {return console.error(请传输需要获取权限的 scope,详见,https://uniapp.dcloud.net.cn/api/other/authorize.html#scope-…...

光学仿真|优化汽车内部照明体验
当我们谈论优化人类感知的内部照明时,我们实际上指的是两个重点领域:安全性和驾驶员体验。如果内部照明可以提供尽可能最佳的体验,驾驶员则能够更好地应对颇具挑战性或意外的驾驶状况,并且减轻疲劳感。除了功能优势外,…...
Spring XML使用CASE WHEN处理SELECT字段
在日常开发中,经常会碰到需要导出的情况。而一些枚举值或者状态一般是定义成整型,这个时候需要对数据进行转换,转换成对应的文本再导出。 在XML中用CASE WHEN来根据不同的查询结果做不同的处理。 比如 SELECT name AS 姓名, age AS 年龄 C…...
关于C#中使用多线程的讨论
关于C#中使用多线程的讨论 C# 中 Thread 调用的函数有返回值 没有输入应该如何解决 如果你想在一个新的线程中调用一个带返回值但没有输入参数的函数,可以使用 Thread 类的委托 ThreadStart 来创建一个新的线程,并在其中调用该函数。然后,你可以使用 Thread 类的 Join 方法…...

工程机械数字孪生可视化平台建设,推动大型装备智能化数字化转型升级
随着信息技术的迅猛发展,数字孪生技术作为一项新兴技术应运而生。数字孪生是指通过数学模型和仿真技术,将实际物理对象与其数字化虚拟模型相互关联,实现对物理对象的全生命周期管理和智能优化。在工程机械领域,巨蟹数科从数字孪生…...

Linux 网络流量监控利器 iftop命令详解及实战
简介 iftop 是什么 在 Linux 系统下即时监控服务器的网络带宽使用情况,有很多工具,比如 iptraf、nethogs 等等,但是推荐使用小巧但功能很强大的 iftop 工具。 iftop 是 Linux 系统一个免费的网卡实时流量监控工具,类似于 top 命令…...

protected by SourceGuardian and requires a SourceGuardian loader ‘ixed.8解决方案
php相关问题 安装程序提示以下内容 遇到某些php程序的安装提示: PHP script ‘/www/wwwroot/zhengban.youyacao.com/install/index.php’ is protected by SourceGuardian and requires a SourceGuardian loader ‘ixed.8.1.lin’ to be installed. 1) Click her…...
KWin、libdrm、DRM从上到下全过程 —— drmModeAddFBxxx(14)
接前一篇文章:KWin、libdrm、DRM从上到下全过程 —— drmModeAddFBxxx(13) 上一回讲完了drivers/gpu/drm/drm_framebuffer.c中的framebuffer_check函数中的第一个for循环,本回继续讲解framebuffer_check()接下来的代码。为了便于理解,再次贴出其源码,如下所示: static …...

2023-macOS下安装anaconda,终端自动会出现(base)字样,如何取消
2023-macOS下安装anaconda,终端自动会出现(base)字样,如何取消 安装后,我们再打开终端,就会自动出现了(base) 就会出现这样子的,让人头大, 所以我们要解决它 具体原因是 安装了anac…...

Nginx搭载负载均衡及前端项目部署
目录 编辑 一.Nginx安装 1.安装所需依赖 2.下载并解压Nginx安装包 3.安装nginx 4.启动Nginx服务 二.Tomcat负载均衡 1.准备环境 1.1 准备两个Tomcat 1.2 修改端口号 1.3 配置Nginx服务器集群 2.效果展示 编辑三.前端项目打包 编辑四.前端项目部署 1.上传项目…...
深度学习——炼丹
学习率调整策略 自定义学习率调整策略 简单版 net MyNet()optimoptim.Adam(net.parameters(),lr0.05) for param_group in optim.param_groups: param_group["lr"] param_group["lr"]*0.5print(param_group["lr"]) #0.25复杂版&#…...

Matlab中的app设计
1.窗口焦点问题: 窗口焦点问题:确保你的应用程序窗口正常处于焦点状态。有时,其他窗口的弹出或焦点切换可能导致应用程序最小化。点击应用程序窗口以确保它处于焦点状态。 窗口管理:确保你的 MATLAB 或操作系统没有未处理的错误或…...
曾经遇到过的无法解释的问题
因为不能直接展示生产数据与生产数据结构,所以写一个简单的例子 class Stu{ private String name; private int age; getter setter constructor 略 } List<Stu> list new ArrayList(); list.add(new Stu("s1",16)); list.add(new Stu("…...

基于uniapp与uview做一个按拼音首字母排序的通讯录页面
效果图: 第一步导入pinyin库并应用,用于区分汉字的拼音首字母 npm i pinyin import pinyin from "pinyin" 完整算法: function getListByPinyinFirstLetter(data) {const newList {};for (const item of data) {let firstLett…...

网络工程师-入门基础课:华为HCIA认证课程介绍
【微/信/公/众/号:厦门微思网络】 华为HCIA试听课程:超级实用,华为VRP系统文件详解 华为HCIA试听课程:不会传输层协议,HCIA都考不过 华为HCIA试听课程:网络工程师的基本功:网络地址转换NAT 一…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...