《QT学习第一篇:QT的概述与安装、信号与槽》

**前引:**本文介绍了QT框架的基本概念、开发环境搭建及核心功能实现。首先阐述了QT作为跨平台GUI应用程序框架的特点,包括其面向对象的架构和高效的开发流程。然后详细说明了开发环境配置步骤,包括编译器安装、QTSDK部署和IDE选择。重点解析了QT项目文件结构、对象树机制和信号槽系统,通过HelloWorld示例展示了控件创建和事件处理的方法。文章还介绍了坐标体系、编码问题解决、快捷键使用等实用技巧,最后讲解了如何查阅官方文档。全文系统性地呈现了QT开发的核心知识体系,为初学者提供了清晰的入门路径。

目录

一、QT介绍

(1)定义

(2)框架理解

(3)优点

二、QT开发环境

[(1)QT SDK下载](#(1)QT SDK下载)

[(2)QT 环境变量配置](#(2)QT 环境变量配置)

(3)第一个项目

(4)注意点

三、配置文件讲解

(1)main.cpp

(2)new_project.cpp

(3)new_project.h

(4)new_project.ui

(5)project_test.pro

(6)项目整体逻辑

[四、第一个项目:Hello World](#四、第一个项目:Hello World)

(1)图形化

(2)代码化

五、对象树

(1)针对内存泄漏

(2)在栈上创建

(3)如何使用自定义类

(4)几大问题

[六、QT Creater中的快捷键](#六、QT Creater中的快捷键)

七、QT文档查询

[八、QT 坐标体系](#八、QT 坐标体系)

九、信号与槽

(1)理解

(2)三要素

(3)connect函数

(1)自定义槽函数(图形)

(2)自定义槽函数(代码)

(3)自定义信号

(4)槽函数带参

(5)解除绑定

(6)多关联性


一、QT介绍

(1)定义

简单直接,从大的范围来说:QT属于桌面客户端开发

客户端:直接和用户打交道的界面,背后可能存在服务端,也可能属于单客户端

而桌面客户端又可以分为:指令式客户端(TUI)图形化客户端(GUI)

而QT就属于:桌面客户端的图形化界面方向,更严格来说属于图形化 用户界⾯ 应⽤程序框架

(2)框架理解

在了解框架之前,我们先看一下库:

库提供了具体的方法实现接口 ,这个工具流程如何设计,完全交给你,你调用库的代码

那么框架恰恰相反:

框架规定了大致的主导流程 ,你需要实现细节,那么需要按照框架的结构,框架调用你的细节代码

(3)优点

至于解释为什么,不重要,它有什么优点,我们拿到知道怎么用就行:

(1)跨平台支持,几所所有平台

(2)接口简单,上手快

(3)开发效率高,快速的构建程序,因为它已经给你固定了大致的主导流程

(4)可以进行嵌入式开发

二、QT开发环境

简单点,它的开发环境需要完成三步:

(1)C++(为例)的编译器,比如gcc,而VS Code这种属于IDE,这需要做一个区分!

(而IDE内部可以自动调用 编译器)

(2)QT SDK,封装好的软件开发工具包

(3)QT的集成开发环境(IDE)

{

(1)官方提供的QT creator------>上手快,适合新手

(2)IDE插件 Visual Studio------>功能强、但配置多

(3)Eclipse

}

那么综上,我们搞一个 IDE 就行了,以QT creator 为例!

(1)QT SDK下载

http://download.qt.io/archive/qt/

选择:5.14.2版本

再选择Windows 桌面应用环境:

下载完成之后双击安装,在SDK组件中选择下面三个:

完成安装:

(2)QT 环境变量配置

为什么设置环境变量:

在Windows上设置Qt的环境变量是为了能够在命令⾏或其他应⽤程序中

直接访问Qt相关的命令和⼯具

找到QT的安装路径,复制,打开"系统环境变量"

在"系统环境变量中"添加(用户或者系统都可以)

(3)第一个项目

(1)文件------>新建项目------>选择"Application",

(2)选择QT构建工具------>老牌稳!(qmake最终生成makefile文件)

QT构建工具选择解释:

(3)选择父类(QWidget)

前面我们提到了框架------>那不就是父类和基类的关系吗?我们是新手,就选择 Qwidget

(4)勾选 generate from

原因:可以以图形化的方式生成界面

(5)选择翻译语言(默认下一步即可)

(6)选择编译器(默认下一步即可)

(7)版本控制(git 或者 none)

(8)完成创建

(4)注意点

在项目的目录中不能出现中文

三、配置文件讲解

(1)main.cpp
cpp 复制代码
#include "new_project.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    new_project w;
    w.show();
    return a.exec();
}

main函数的命令行参数就不多说了:参数个数、对应参数内容

QApplication a(argc, argv):创建QT框架对象,就是实例化一份QT资源

new_project w:要实现的具体功能对象

w.show():触发绘制逻辑指令

return a.exec():一直循环显示窗口

作用:项目的整个调用逻辑顺序

(2)new_project.cpp
cpp 复制代码
#include "new_project.h"
#include "ui_new_project.h"

//第一部分
new_project::new_project(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::new_project)
{
    ui->setupUi(this);
}

//第二部分
new_project::~new_project()
{
    delete ui;
}

第一部分:完成自定义功能窗口的初始化工作

第二部分:对你的自定义窗口资源进行释放

那么为什么不直接利用 main.cpp 实例化 new_project 对象自动调用构造和析构呢?

作用:通过自定义构造和析构提高工程扩展性和可维护性,将类的声明和定义分离

(3)new_project.h
cpp 复制代码
#ifndef NEW_PROJECT_H
#define NEW_PROJECT_H

#include <QWidget>

//第一部分
QT_BEGIN_NAMESPACE
namespace Ui { class new_project; }
QT_END_NAMESPACE

//第二部分
class new_project : public QWidget
{
    Q_OBJECT

public:
    new_project(QWidget *parent = nullptr);
    ~new_project();

private:
    Ui::new_project *ui;
};
#endif // NEW_PROJECT_H

首先这是一个头文件,开头的#ifndef....就是保护相同的头文件只出现一次

第一部分:定义了一个命令空间 Ui,里面包含了 new_project 类

第二部分:new_project 的具体实现

那么为什么要这么做呢?需要额外定义一个命令空间?

前置声明:只告诉编译器 "存在一个Ui::new_project类",提高编译效率

作用:

它像项目整个蓝图描述,只标注 "项目继承了整个父类、有哪些函数、有哪些成员变量",

不包含具体的实现细节(那是.cpp的事)

(4)new_project.ui

作用:

  • 提效:可视化设计界面,省去所有手写布局 / 控件属性的繁琐代码;
  • 解耦:界面布局和功能逻辑分离,改界面不影响功能,改功能不影响界面;

(因为.cpp是管具体的功能实现,那么 .ui 就是管这个图形化界面排版)

(5)project_test.pro

核心配置文件,由qmake工具解析,其核心作用是定义项目的构建规则

告诉 qmake 如何编译代码、链接库、处理资源,最终生成可执行文件

(6)项目整体逻辑

编译阶段:

qmake 把.pro的配置、.ui的设计(图形排版)、.h/.cpp的代码(功能实现),转为可执行文件

运行阶段:

main函数启动,先初始化 QT 框架,再加载自定义界面,最后启动事件循环响应操作,执行析构

四、第一个项目:Hello World

(1)图形化

直接在 .ui 文件中,选择 Label 控件(标签意思),调整大小后,输入要显示的内容

(2)直接运行即可

(2)代码化

在构造函数中创建一个 Qlabel 对象(栈、堆都可以),然后使用里面的接口即可

为什么要不在 new_project.h 中实现?

new_project.h 中是声明,可能需要向外暴露,但在实际中,具体的实现和声明应该是分离的

为什么 Qlabel 的参数是 this ?

很好理解,谁调用的这个类那么this就指向的谁,在外面是 w(自定义功能具体实现) 调用的,所以它的具体含义是:将 Qlabel 这个控件交给 this 也就是 w 进行管理

五、对象树

例如:

(1)针对内存泄漏

那么这样 new 出来的对象需要担心内存泄漏吗?

首先明白:堆上内存什么时候结束?程序结束自动释放或者手动,那么这里将QLabel挂在了 w 上面,形成一棵N叉树,可以按照(派生类->基类)的内存释放顺序理解!树状结构的对象!

cpp 复制代码
QApplication a(顶层根节点)
    ↳ new_project w(窗口,父对象)
        ↳ QLabel label(子对象,父是w)

同时也表明了:QApplication 是框架,其余只是添加"框架"的枝干!

(2)在栈上创建

完全不用担心,我们先看另外一种形式的创建:在栈上创建,可以看见没有输出

具体原因:我们是在 new_project 类对象 w 构造中创建的,所以 w 在当前栈一销毁,就没了

(3)如何使用自定义类

(1)首先在文件里面创建一个C++的类,自定义类名称

(2)将这个类加入到对象树

(3)实现具体的方法

(4)几大问题

为什么 new_project 类实例的 w 对象,它的构造类型是 Qwidget 类型?

首先原本 this 指针的类型是 new_project * 类型,而子类指针可以安全的转化为父类指针也就是 Qwidget * 类型,从而 this 指针类型可以进行一个转化!

一个类(自定义或内置生类)是如何加入到对象树的?

**原则:**只有继承了QObject的类,才能被加入 QT 对象树(QWidget继承⾃QObject)

内置类:内置类天上继承了 QObject,可以直接用,不用管

自定义类:这个自定义类现在是派生类,只有让派生类继承某个内置类才能进入对象树

字符串编码显示问题?

首先一个汉字占多少字节取决于编码方式:

(1)GBK------>2个字节一个汉字

(2)UTF-8------>3个字节一个汉字

在QT中,可能因为编码格式不统一(编码和显示器)会形成乱码,所以将 cout 切换为 qDebug

例如:

同时:

qDebug 在调试阶段可看见打印内容,但是在发布版本中是不显示的,很方便用来打印日志

六、QT Creater中的快捷键

注释:Ctrl + /

运行:Ctrl + R

编译:Ctrl + B

字体缩放:Ctrl + 鼠标滑轮

查找:Ctrl + F

整行移动:Ctrl + shift + 向上箭头/向下箭头

自动对齐:Ctrl + i

同类的源文件和头文件切换:F4

立刻给函数声明实现定义:选中这个函数 + Alt + Enter

七、QT文档查询

(1)直接打开assistant.exe

(2)帮助

八、QT 坐标体系

对于嵌套窗⼝,其坐标是相对于⽗窗⼝来说的

通过 move(x,y)内置接口来完成相较于父窗口的移动

例如:

九、信号与槽

(1)理解

触发了什么信号------>执行什么样的方法

(2)三要素

信号源:由哪个控件触发

信号的类型:比如点击信号、输入信号........本质也是一个类

槽:信号处理的方法------>函数

(3)connect函数

那么如何把信号与槽关联起来?

用 **connect()**接口将二者进行关联:它属于QObject的接口,由于具有继承关系,可以直接用!

接口原型:

cpp 复制代码
connect(
    发送者对象指针,    // 参数1:谁发信号
    &发送者类::信号函数名, // 参数2:发什么信号
    接收者对象指针,    // 参数3:谁接收信号
    &接收者类::槽函数名  // 参数4:接收后执行什么(或Lambda表达式)
);

简单易上手:

第一个参数:如果是内置信号,就是控件;自定义信号就是 this(因为是当前 Widget 对象成员)

第三个参数:槽函数属于哪个对象,就写哪个对象的指针

参数说明:

(1)**参数:**发送对象的指针

举例:如点击按钮,指针指向的是QPushbutton按钮(堆上存储),含义就是由QPushbutton发出

注意:必须是QObject*类型(或其子类指针,如QPushButton*QWidget*

(2)**参数:**要发出的信号

举例:信号本质也是一个类函数,那么比如点击,不就是点击里面的一个信号函数吗:

控件 信号函数名 含义
QPushButton clicked() 按钮被点击
QLineEdit textChanged(QString) 输入框文本改变
QCheckBox toggled(bool) 复选框选中状态改变

connect ( btn , &QPushButton::clicked , ...); // 参数2是按钮的"点击信号"

注意:类型要求: 必须是「发送者类的成员函数指针」,格式是&类名::信号函数名

(3)**参数 :**信号接收者

举例:接收到了某个信号,谁来处理?那不当前窗口类型的指针吗?比如 this

注意:和参数 1 一样,必须是QObject*类型(或其子类指针)。

(4)**参数:**槽函数或者Lambda表达式

例如:(1)直接使用接受者里面的成员函数方法

cpp 复制代码
// new_project.h里声明了void onBtnClicked();
connect(btn, &QPushButton::clicked, this, &new_project::onBtnClicked);

(2)Lambda表达式

cpp 复制代码
connect(btn, &QPushButton::clicked, this, [=]() {
    // 这里直接写槽逻辑,比如修改标签文本
    label->setText("按钮被点击了!");
});

注意:类型要求 :「接收者类的成员函数指针」,格式是&接收者类::槽函数名

(1)自定义槽函数(图形)

我们可以直接在 .ui 文件中选择操作,比如点击操作,然后右键"槽函数"

然后使用 ui 来操作这个点击按钮,就比如 " ui -> pushbutton"就拿到了这个点击按钮控件,而通过图形化自定义槽函数我们只需要实现 槽函数即可,QT自动完成了connect连接!

为什么是通过 ui 操作,而不是 this?

可以看到我们自定义的细节功能 Widget 只是 ui 里面的一个类,而我们的图形化操作是直接属于 ui

ui管 "可视化拖的控件",this管 "自己写的 Widget 类本身"

(2)自定义槽函数(代码)

通过代码实现的话,就是通过 connect 接口进行关联信号和槽:

第一个参数:哪里来的信号,这个我们已经具备,就是 ui 里面的(控件名称)

第二个参数:触发的是什么信号,很明显,是这个控件的点击信号(为例)

第三个参数:信号的接受者,就是当前窗口 this

第四个参数:任务执行函数,所以我们需要自定义一个任务函数

效果例如:

(3)自定义信号

自定义信号只需要在类里面增加一个:signals修饰,信号的本质就是一个成员函数

这里的 emit 不用加域的原因是,它是在Widget函数里面使用的,自带域

(4)槽函数带参

为什么槽函数支持带参?让信号在触发时,将参数传给槽函数------>信号给槽函数传参

**规则1:**信号的参数个数 >= 槽函数的参数个数

**规则2:**槽函数会从左到右与信号进行参数匹配,即槽函数可以不全部获取完信号的参数

例如:

那么在connect后,槽函数会从左到右自动匹配参数类型:

效果:

(5)解除绑定

绑定信号和槽函数是:connect

解除信号和槽保证参数相同,将 connect 改成 disconnect

(6)多关联性

即一个信号可以对应多个槽函数,那么一个槽函数也可以被多个信号同时触发

相关推荐
花千树-0102 小时前
用 Java 实现 RAG 组件化:从 PDF 加载到智能问答全流程
java·开发语言·人工智能·langchain·pdf·aigc·ai编程
AI帮小忙2 小时前
CTF安全竞赛能力矩阵
开发语言·php
2301_789015622 小时前
C++11新增特性:列表初始化&左值引用&右值引用&万能引用&移动构造&移动赋值&引用折叠&完美转发
c语言·开发语言·c++·c++11
2301_805962932 小时前
树莓派学习2-读取I2C设备数据
学习
赫瑞2 小时前
Java中的进制转换
java·开发语言
2301_805962932 小时前
树莓派学习1-I2C配置与设备状态检测
嵌入式硬件·学习
lsx2024063 小时前
jQuery 删除元素
开发语言
紫金修道10 小时前
【DeepAgent】概述
开发语言·数据库·python
Via_Neo10 小时前
JAVA中以2为底的对数表示方式
java·开发语言