【QT】第一个QT程序 || 对象树 || 编码时的注意事项

发布于:2025-06-30 ⋅ 阅读:(26) ⋅ 点赞:(0)

一、编写第一个 Qt 程序

1. 开发环境搭建

  • 安装 Qt Creator(推荐使用官方在线安装器)
  • 安装 Qt 库(如 Qt 5.15.2 或 Qt 6.x)
  • 配置编译器(MinGW / MSVC / GCC)

2. 创建一个简单的 Qt GUI 应用程序

  1. 打开 Qt Creator,点击“文件” -> “新建文件或项目”
  2. 选择应用程序类型:
    • Application -> Qt Widgets Application
  3. 设置项目名称和路径
  4. 选择构建套件(Kit)
  5. 输入类名(默认为 QApplicationQMainWindow/QDialog/QWidget
  6. 完成创建

3. 程序结构分析

生成的主要文件包括:

.pro文件

项目配置文件,定义构建参数

main.cpp

程序入口点

mainWindow.h/cpp

主窗口类定义和实现

ui_mainwindow.h

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. 创建多个对象并建立父子关系
  2. 打印父子结构
  3. 删除父对象后检查子对象是否也被删除
  4. 使用调试器或日志辅助验证

示例代码:手动构建对象树并验证

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. 多线程编程

  • 推荐使用 QThreadQtConcurrent
  • 避免直接操作 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,除非必要

网站公告

今日签到

点亮在社区的每一天
去签到