一、編寫第一個 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_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic: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_OBJECTpublic: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 的 parentroot->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,除非必要