一、编写第一个 Qt 程序
1. 开发环境搭建
- 安装 Qt Creator(推荐使用官方在线安装器)
- 安装 Qt 库(如 Qt 5.15.2 或 Qt 6.x)
- 配置编译器(MinGW / MSVC / GCC)
2. 创建一个简单的 Qt GUI 应用程序
- 打开 Qt Creator,点击“文件” -> “新建文件或项目”
- 选择应用程序类型:
- Application -> Qt Widgets Application
- 设置项目名称和路径
- 选择构建套件(Kit)
- 输入类名(默认为
QApplication
、QMainWindow
/QDialog
/QWidget
) - 完成创建
3. 程序结构分析
生成的主要文件包括:
|
项目配置文件,定义构建参数 |
|
程序入口点 |
|
主窗口类定义和实现 |
|
UI 自动生成的代码(由 .ui 文件转换而来) |
示例代码:
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec(); // 进入主事件循环
}
#include "widget.h" // 创建生成时的文件
#include "ui_widget.h"
#include <QLabel> // 包含标签的头文件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this); // 将form file生成的界面和我们当前的widget进行关联起来
// 创建对象的两种方法
// QLabel label; // 在栈上创建
QLabel* label = new QLabel(this); // 在堆上创建,推荐这种方法,还要传递 一个 this,给当前这个 lable 对象指定 父对象
// 1. 设置标签内容
label->setText((QString)("显式 Hello world"));
label->setText("隐式 Hello World"); // QString 也提供了 C 风格字符串作为参数的构造函数来不显示构造 QString
// 注意:由于QString 对应的头文件,已经被很多 Qt 内置的其他类给间接包含了.因此一般不需要显式包含 QString 头文件
// 这里虽然有两次 setText,但是下面内容会覆盖上面内容
// 2. 设置窗口大小
setFixedSize(500, 400);
// 3. 设置字体大小
QFont font("楷体", 16);
label->setFont(font);
// 4. 设置标签内容显式位置
label->move(200, 150);
// 5. 设计标签字体颜色
label->setStyleSheet("color:blue");
}
Widget::~Widget()
{
delete ui;
}
4.按钮实现第一个代码
图形化界面实现
1.双击:"widget.ui" 文件
2.拖拽控件至 ui 界面窗口并修改内容
- 虽然那里有好几个按钮,但是我们这里用 Push Button(普通按钮)
3.构建并运行,效果如下所示
这里的按钮的确可以点击,但是却没有任何反应,这个就设计到我们后面学的信号槽知识,后面会说的
QT 的信号槽机制:本质上就是给按钮的点击操作,关联上一个处理函数,当用户点击的时候,就会执行这个处理函数
这里我们的按钮没有任何功能,假如我们要实现一定的功能,那该怎么做呢?
打开 widget.ui 文件,查看设计的右下角,则有
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 按钮的点击操作 -- 信号槽
// 在 Linux 网络编程那也有个connect 函数,那里用来给 TCP socket 建立连接的,写 TCP 客户端的时候,就需要先建立连接才能读写数据
// ui->pushButton:谁发出的信号
// &QPushButton::clicked:发出了啥信号,点击按钮的时候自动触发该信号
// this: 谁来处理这个信号
// Widget::handle:具体怎么处理
connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick); // 访问到 form file(ui 文件)中创建的控件
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleClick()
{
if(ui->pushButton->text() == "Hello World"){
ui->pushButton->setText("Hello IsLand");
}else{
ui->pushButton->setText("Hello World");
}
}
返回上级目录查看 ui_widget.h 文件
5.纯代码实现按钮
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPushButton>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
void handleClick();
~Widget();
private:
Ui::Widget *ui;
QPushButton* myButton;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
myButton = new QPushButton(this);
myButton->setText("Hello World");
connect(myButton, &QPushButton::clicked, this, &Widget::handleClick); // 访问到 form file(ui 文件)中创建的控件
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleClick()
{
if(myButton->text() == QString("Hello World")){
myButton->setText("Hello IsLand");
}else{
myButton->setText("Hello World");
}
}
实现效果:
两个版本比较:
- 图形化实现:此时按钮对象不需要咱们自己 new。new 对象的操作已经是被 Qt 自动生成了而且这个按钮对象,已经作为 ui 对象里的一个成员变量了,也无需作为 Widget 的成员
- 纯代码实现:按钮对象是咱们自己 new 的,为了保证其他函数中能够访问到这个变量,就需要把按钮对象,设定为 Widget 类的成员变量
实际开发中,是通过代码的方式构造界面为主,还是通过图形化界面的方式构造界面为主??
- 这两种都很主要,难分主次!!
- 如果你当前程序界面,界面内容是比较固定的,此时就会以 图形化 的方式来构造界面
- 但是如果你的程序界面,经常要动态变化,此时就会以 代码 的方式来构造界面
- 反正这两种方式哪种方便用哪个,也可以配合来使用
二、对象树机制(Parent-Child Object Tree)
1. 概念
Qt 提供了一种基于父子关系的对象管理机制 —— 对象树机制 。
当一个 QObject 子类对象被创建时,可以指定一个父对象(parent),该对象会自动加入到父对象的子对象列表中。
2. 特点
- 当父对象被删除时,其所有子对象也会被自动删除。
- 避免手动 delete,减少内存泄漏风险。
- 只适用于继承自
QObject
的类。QWidget *parent = new QWidget; QPushButton *button = new QPushButton("Click Me", parent); // parent 作为按钮的父对象 // 不需要手动 delete button delete parent; // 自动 delete button
3. 注意事项
- 如果使用了
setParent()
方法,也要注意生命周期管理。 - 对于栈上对象(如局部变量),不能作为父对象传入堆上的对象。
三、验证对象树
Qt 的对象树机制是基于 QObject
类的父子关系实现的:
- 当一个 QObject 子类对象被创建时,可以指定一个父对象。
- 父对象被销毁时,会自动销毁所有子对象(递归)。
- 可以通过
parent()
和children()
方法查看父子关系。
验证目标:
- 创建多个对象并建立父子关系
- 打印父子结构
- 删除父对象后检查子对象是否也被删除
- 使用调试器或日志辅助验证
示例代码:手动构建对象树并验证
1. 创建一个 Qt Widgets 应用(Qt Widgets Application)
在你的主窗口类中编写如下代码:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDebug>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
void buildObjectTree();
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) {
buildObjectTree();
}
MainWindow::~MainWindow() {
qDebug() << "MainWindow destroyed";
}
void MainWindow::buildObjectTree() {
QObject* root = new QObject(this); // this 是 MainWindow,作为 root 的 parent
root->setObjectName("Root");
QObject* child1 = new QObject(root);
child1->setObjectName("Child1");
QObject* child2 = new QObject(root);
child2->setObjectName("Child2");
QObject* grandchild = new QObject(child1);
grandchild->setObjectName("Grandchild");
qDebug() << "Parent of" << grandchild->objectName() << "is" << grandchild->parent()->objectName();
qDebug() << "Children of" << root->objectName() << ":";
foreach(QObject* obj, root->children()) {
qDebug() << " - " << obj->objectName();
}
}
main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
输出日志验证对象树结构
运行程序后,在 Qt Creator 的 应用程序输出 面板中可以看到类似如下内容:
Parent of "Grandchild" is "Child1"
Children of "Root" :
- "Child1"
- "Child2"
说明对象树结构正确建立。
四、Qt 编程注意事项
1. 使用智能指针
虽然 Qt 有对象树机制,但在某些情况下建议使用 C++ 标准库中的智能指针:
std::unique_ptr
:独占资源所有权std::shared_ptr
:共享资源所有权
例如:
auto ptr = std::make_unique<QWidget>();
2. 避免野指针
- 在删除对象前确保没有其他地方引用它。
- 使用 Qt 的父子对象机制来管理内存。
3. 信号与槽机制
- Qt 提供了强大的通信机制:
connect()
函数连接信号和槽函数。 - 支持跨线程通信(需使用
Qt::QueuedConnection
)
示例:
connect(button, &QPushButton::clicked, this, &MyClass::handleClick);
4. 多线程编程
- 推荐使用
QThread
或QtConcurrent
- 避免直接操作 UI 控件在非主线程中
5. 资源释放
- 图片、文件等资源要记得关闭或释放
- 使用 RAII(资源获取即初始化)原则进行管理
五个、、内存泄露问题排查与预防
1. 内存泄露常见原因
忘记 delete |
动态分配内存后未释放 |
循环引用 |
A 引用 B,B 引用 A,导致无法释放 |
未正确使用父子对象 |
父对象未正确设置,导致子对象未被自动释放 |
信号槽未断开 |
长生命周期对象持有短生命周期对象的连接 |
2. 内存检测工具
- Windows 平台 :
- Visual Leak Detector (VLD)
- CRT Debug Heap(调试模式下使用
_CrtDumpMemoryLeaks()
)
- Linux/macOS 平台 :
- Valgrind
- AddressSanitizer
3. 使用 Qt 自带机制辅助检测
- 启用调试输出
- 使用
qDebug()
输出日志帮助定位问题 - 使用
QScopedPointer
(已过时,建议用std::unique_ptr
)
4. 预防策略
- 尽量使用智能指针或 Qt 的父子对象机制
- 使用 RAII 技术封装资源管理
- 定期使用内存检测工具测试
- 避免手动 new/delete,除非必要