一、Qt 基础概念
1. 什么是 Qt
Qt 是一个 C++ 框架,主要用来写桌面程序、嵌入式程序、移动端程序。核心能力:
- GUI:图形界面(按钮、窗口、菜单)
- 网络:TCP/UDP/HTTP
- 数据库:SQLite/MySQL
- 多媒体:音频/视频
- 多线程:线程、线程池
- 串口通信:QSerialPort
- XML/JSON 解析
- 跨平台:Windows/Linux/Mac/Android/iOS 一套代码
2. Qt 的模块
Qt Core ← 核心(信号槽、容器、文件、线程、定时器)
Qt GUI ← 图形界面基础(窗口、事件、字体、图片)
Qt Widgets ← 传统控件(按钮、输入框、表格)
Qt QML ← QML 引擎
Qt Quick ← QML 现代界面
Qt Network ← 网络(TCP/UDP/HTTP)
Qt SQL ← 数据库
Qt SerialPort← 串口通信
Qt Multimedia← 音视频
Qt Charts ← 图表
Qt 3D ← 3D渲染
Qt WebEngine ← 内嵌浏览器
3. pro 文件(项目文件)
QT += core gui widgets # 用到的Qt模块
TARGET = MyApp # 生成的exe名字
TEMPLATE = app # 项目类型:app=可执行程序 lib=库 subdirs=多子项目
SOURCES += main.cpp mainwindow.cpp # cpp文件
HEADERS += mainwindow.h # h文件
FORMS += mainwindow.ui # ui文件(Designer设计的界面)
RESOURCES += qml/qml.qrc # 资源文件(QML、图片等)
LIBS += -L$$PWD/ -lMyLib # 链接外部库
INCLUDEPATH += $$PWD/include # 头文件搜索路径
# 平台判断
win32 { RC_FILE += app_version.rc } # Windows资源文件
linux { LIBS += -lpthread } # Linux链接线程库
# 编译器判断
msvc { QMAKE_CXXFLAGS += /utf-8 } # MSVC编码设置
mingw { QMAKE_CXXFLAGS += -std=c++11 } # MinGW标准
# 条件编译
CONFIG += c++11 # 启用C++11
CONFIG += c++17 # 启用C++17
CONFIG += debug_and_release # 同时生成debug和release
DEFINES += MY_FLAG # 定义预处理宏
4. qmake 变量
$$PWD # 当前pro文件所在目录
$$OUT_PWD # 构建输出目录
$$[QT_INSTALL_LIBS] # Qt库安装路径
$$basename(HEADER) # 取文件名
$$dirname(HEADER) # 取目录名
5. 前向声明(Forward Declaration)
// mainwindow.h 里
class QQuickWidget; // 只声明,不include
class TrdpQmlBridge;
class MainWindow : public QMainWindow {
QQuickWidget *mQuickWidget; // 只用指针,不需要知道完整定义
TrdpQmlBridge *mBridge;
};
为什么用前向声明 :减少头文件依赖,加快编译。只要用指针或引用,就不需要 #include。
6. 头文件保护
// 方式1:传统方式
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
// ... 类定义
#endif
// 方式2:简洁方式(推荐)
#pragma once
// ... 类定义
作用:防止头文件被重复包含。
二、QObject 核心机制
7. QObject 父子关系
QObject *parent = new QObject();
QObject *child1 = new QObject(parent); // child1的父对象是parent
QObject *child2 = new QObject(parent); // child2的父对象是parent
delete parent;
// parent被删除时,child1和child2自动被删除
// 不需要手动delete子对象
内存管理:父对象析构时自动析构所有子对象。这是 Qt 的垃圾回收机制。
8. 对象树
QApplication ← 全局唯一
└── MainWindow ← 主窗口
├── QQuickWidget ← QML容器
│ └── QQmlEngine ← QML引擎
├── TrdpQmlBridge ← 桥接对象
│ └── TrdpServiceHost ← 服务管理
│ └── TrdpWorker ← 工作线程对象
└── LogManager ← 日志管理器
9. objectName
QObject *obj = new QObject();
obj->setObjectName("myButton"); // 设置名字
QString name = obj->objectName(); // 获取名字 "myButton"
用途:调试时标识对象,QML 里也可以通过 objectName 查找对象。
10. findChild / findChildren
// 在子对象树中按名字查找
QPushButton *btn = parent->findChild<QPushButton*>("myButton");
// 查找所有子对象
QList<QPushButton*> allButtons = parent->findChildren<QPushButton*>();
11. connect 的第五个参数(连接类型)
connect(sender, &Sender::signal,
receiver, &Receiver::slot,
Qt::AutoConnection); // 默认值
| 连接类型 | 意义 |
|---|---|
Qt::AutoConnection |
同线程直接调用,不同线程排队调用(默认) |
Qt::DirectConnection |
无论是否同线程,都在发送方线程直接调用 |
Qt::QueuedConnection |
无论是否同线程,都在接收方线程排队调用 |
Qt::BlockingQueuedConnection |
排队调用,发送方阻塞等待接收方执行完 |
Qt::UniqueConnection |
防止重复连接,和其他类型组合使用 |
// 防止重复连接
connect(btn, &QPushButton::clicked, this, &MyClass::onClicked, Qt::UniqueConnection);
// 多次调用这条connect,只会连接一次
12. 信号槽返回值
// 信号没有返回值(void)
signals:
void dataReady(QByteArray data);
// 槽可以有返回值,但QueuedConnection下拿不到
// DirectConnection可以
connect(sender, &Sender::request, receiver, &Receiver::process, Qt::DirectConnection);
13. 一个信号连多个槽
connect(timer, &QTimer::timeout, this, &MyClass::updateUI); // 槽1
connect(timer, &QTimer::timeout, this, &MyClass::checkStatus); // 槽2
// 两个槽都会被调用,顺序不确定
14. 多个信号连一个槽
connect(btn1, &QPushButton::clicked, this, &MyClass::onAnyButtonClicked);
connect(btn2, &QPushButton::clicked, this, &MyClass::onAnyButtonClicked);
// 任意一个按钮点击都会调用同一个槽
15. 信号连信号
connect(btn, &QPushButton::clicked, this, &MyClass::clicked);
// 按钮的clicked → 转发为MyClass的clicked信号
// 相当于中继/转发
三、控件(Widgets)
16. 基础控件
// 按钮
QPushButton *btn = new QPushButton("确定");
QPushButton *btn2 = new QPushButton(QIcon("icon.png"), "带图标的按钮");
connect(btn, &QPushButton::clicked, this, &MyClass::onClicked);
// 标签
QLabel *label = new QLabel("文字");
label->setAlignment(Qt::AlignCenter); // 居中
label->setWordWrap(true); // 自动换行
label->setPixmap(QPixmap("image.png")); // 显示图片
// 单行输入框
QLineEdit *edit = new QLineEdit();
edit->setPlaceholderText("请输入IP地址"); // 占位文字
edit->setEchoMode(QLineEdit::Password); // 密码模式
edit->setValidator(new QIntValidator(0, 65535)); // 限制只能输入0-65535
edit->setMaxLength(15); // 最大长度
QString text = edit->text(); // 获取输入内容
// 多行文本框
QPlainTextEdit *plainEdit = new QPlainTextEdit();
plainEdit->setReadOnly(true); // 只读
plainEdit->appendPlainText("追加一行"); // 追加文字
plainEdit->clear(); // 清空
int lines = plainEdit->blockCount(); // 行数
// QTextEdit:富文本(支持HTML)
QTextEdit *richEdit = new QTextEdit();
richEdit->setHtml("<b>粗体</b> <font color='red'>红色</font>");
// 下拉框
QComboBox *combo = new QComboBox();
combo->addItem("选项1");
combo->addItem("选项2", QVariant(100)); // 带用户数据
combo->addItems({"A", "B", "C"}); // 批量添加
QString current = combo->currentText(); // 当前文字
QVariant data = combo->currentData(); // 当前用户数据
// 狂选框
QCheckBox *check = new CheckBox("同意协议");
check->setChecked(true); // 设置选中
bool isChecked = check->isChecked(); // 是否选中
// 单选按钮
QRadioButton *radio1 = new QRadioButton("选项A");
QRadioButton *radio2 = new QRadioButton("选项B");
QButtonGroup *group = new QButtonGroup(this);
group->addButton(radio1, 1); // id=1
group->addButton(radio2, 2); // id=2
// 数字输入框
QSpinBox *spin = new QSpinBox();
spin->setRange(0, 100); // 范围
spin->setValue(50); // 当前值
spin->setSingleStep(5); // 步长
QDoubleSpinBox *doubleSpin = new QDoubleSpinBox();
doubleSpin->setDecimals(2); // 小数位数
// 滑块
QSlider *slider = new QSlider(Qt::Horizontal);
slider->setRange(0, 100);
slider->setValue(50);
// 进度条
QProgressBar *progress = new QProgressBar();
progress->setRange(0, 100);
progress->setValue(75); // 75%
// 列表
QListWidget *listWidget = new QListWidget();
listWidget->addItem("项目1");
listWidget->addItem("项目2");
// 取出选中项
QListWidgetItem *item = listWidget->currentItem();
QString text = item->text();
// 表格
QTableWidget *table = new QTableWidget(10, 3); // 10行3列
table->setHorizontalHeaderLabels({"姓名", "年龄", "城市"});
table->setItem(0, 0, new QTableWidgetItem("张三"));
// 树
QTreeWidget *tree = new QTreeWidget();
tree->setHeaderLabels({"名称", "值"});
QTreeWidgetItem *root = new QTreeWidgetItem(tree, {"根节点", ""});
QTreeWidgetItem *child = new QTreeWidgetItem(root, {"子节点", "123"});
// 分割器(可拖拽调整大小)
QSplitter *splitter = new QSplitter(Qt::Horizontal);
splitter->addWidget(leftWidget);
splitter->addWidget(rightWidget);
// 选项卡
QTabWidget *tabs = new QTabWidget();
tabs->addTab(page1, "第一页");
tabs->addTab(page2, "第二页");
// 工具栏
QToolBar *toolbar = addToolBar("工具栏");
toolbar->addAction("新建", this, &MyClass::onNew);
toolbar->addAction(QIcon("open.png"), "打开", this, &MyClass::onOpen);
// 状态栏
statusBar()->showMessage("就绪", 3000); // 显示3秒
// 菜单栏
QMenu *fileMenu = menuBar()->addMenu("文件(&F)"); // &F 表示快捷键 Alt+F
fileMenu->addAction("新建(&N)", this, &MyClass::onNew, QKeySequence::New);
fileMenu->addAction("打开(&O)", this, &MyClass::onOpen, QKeySequence::Open);
fileMenu->addSeparator(); // 分隔线
fileMenu->addAction("退出(&X)", this, &QWidget::close);
17. 对话框
// 消息框
QMessageBox::information(this, "提示", "操作成功");
QMessageBox::warning(this, "警告", "磁盘空间不足");
QMessageBox::critical(this, "错误", "连接失败");
int ret = QMessageBox::question(this, "确认", "确定要退出吗?",
QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::Yes) { /* 退出 */ }
// 文件选择对话框
QString path = QFileDialog::getOpenFileName(this, "选择文件", "",
"文本文件 (*.txt);;所有文件 (*.*)");
QString dir = QFileDialog::getExistingDirectory(this, "选择目录");
QString savePath = QFileDialog::getSaveFileName(this, "保存文件");
// 输入对话框
QString text = QInputDialog::getText(this, "输入", "请输入姓名:");
int num = QInputDialog::getInt(this, "输入", "请输入数量:", 10, 0, 100);
double value = QInputDialog::getDouble(this, "输入", "请输入价格:", 9.99, 0, 1000, 2);
QString item = QInputDialog::getItem(this, "选择", "请选择:", {"A", "B", "C"});
// 颜色选择对话框
QColor color = QColorDialog::getColor(Qt::white, this, "选择颜色");
// 字体选择对话框
QFont font = QFontDialog::getFont(nullptr, this, "选择字体");
// 进度对话框
QProgressDialog progress("正在处理...", "取消", 0, 100, this);
progress.setWindowModality(Qt::WindowModal); // 模态,阻塞父窗口
for (int i = 0; i <= 100; i++) {
progress.setValue(i);
if (progress.wasCanceled()) break;
QThread::msleep(50);
}
18. 自定义对话框
class MyDialog : public QDialog {
Q_OBJECT
public:
MyDialog(QWidget *parent = nullptr) : QDialog(parent) {
QVBoxLayout *layout = new QVBoxLayout(this);
QLineEdit *edit = new QLineEdit();
QPushButton *okBtn = new QPushButton("确定");
QPushButton *cancelBtn = new QPushButton("取消");
layout->addWidget(edit);
layout->addWidget(okBtn);
layout->addWidget(cancelBtn);
connect(okBtn, &QPushButton::clicked, this, &QDialog::accept); // 结果=Accepted
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject); // 结果=Rejected
}
};
// 使用
MyDialog dlg;
if (dlg.exec() == QDialog::Accepted) {
qDebug() << "用户点了确定";
}
19. 布局详解
// 边距
layout->setContentsMargins(10, 10, 10, 10); // 左、上、右、下
layout->setSpacing(8); // 控件之间的间距
// 弹簧
layout->addStretch(); // 添加弹性空间
layout->insertStretch(0); // 在位置0插入弹性空间
// 对齐
layout->addWidget(btn, 0, Qt::AlignRight); // 右对齐
// 拉伸因子
QHBoxLayout *hLayout = new QHBoxLayout();
hLayout->addWidget(leftWidget, 1); // 占1份
hLayout->addWidget(rightWidget, 2); // 占2份(右边是左边的两倍宽)
// QFormLayout:表单布局(标签+输入框)
QFormLayout *form = new QFormLayout();
form->addRow("IP地址:", ipEdit);
form->addRow("端口号:", portEdit);
form->addRow("密码:", passwordEdit);
// QStackedLayout:叠放布局(同一时间只显示一个)
QStackedLayout *stack = new QStackedLayout();
stack->addWidget(page1); // 索引0
stack->addWidget(page2); // 索引1
stack->setCurrentIndex(1); // 显示page2
20. QSS 样式表(类似 CSS)
// 全局样式
qApp->setStyleSheet("QPushButton { background: #333; color: white; border-radius: 5px; }");
// 单个控件样式
btn->setStyleSheet("background-color: red; color: white; padding: 10px;");
// 选择器
/*
QPushButton#myBtn { } id选择器
QPushButton:hover { } 悬停状态
QPushButton:pressed { } 按下状态
QPushButton:checked { } 选中状态(ToggleButton)
QLineEdit:focus { } 获得焦点状态
QComboBox::drop-down { } 子控件选择器
*/
常见属性:
background-color: #333; /* 背景色 */
color: white; /* 文字颜色 */
border: 1px solid #666; /* 边框 */
border-radius: 5px; /* 圆角 */
padding: 10px; /* 内边距 */
margin: 5px; /* 外边距 */
font-size: 14px; /* 字号 */
font-weight: bold; /* 粗体 */
font-family: "Microsoft YaHei"; /* 字体 */
21. UI 文件(Designer)
// 使用UI文件
#include "ui_mainwindow.h"
class MainWindow : public QMainWindow {
Ui::MainWindow *ui;
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this); // 加载UI文件定义的界面
// UI里的控件通过 ui->控件名 访问
ui->pushButton->setText("点击我");
connect(ui->lineEdit, &QLineEdit::textChanged, this, &MyClass::onTextChanged);
}
MainWindow::~MainWindow() {
delete ui;
}
22. 自定义控件
// 继承已有控件
class MyButton : public QPushButton {
Q_OBJECT
public:
MyButton(const QString &text, QWidget *parent = nullptr)
: QPushButton(text, parent)
{
setStyleSheet("background: #1f7a63; color: white; border-radius: 10px;");
}
protected:
void paintEvent(QPaintEvent *event) override {
QPushButton::paintEvent(event); // 先画默认样式
// 再画自己的东西
QPainter painter(this);
painter.setPen(Qt::red);
painter.drawText(rect(), Qt::AlignCenter, "自定义");
}
};
// 继承QWidget完全自定义
class MyChart : public QWidget {
Q_OBJECT
public:
explicit MyChart(QWidget *parent = nullptr) : QWidget(parent) {}
protected:
void paintEvent(QPaintEvent *event) override {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing); // 抗锯齿
painter.setPen(QPen(Qt::blue, 2));
painter.drawLine(0, 0, width(), height()); // 画线
painter.setBrush(Qt::red);
painter.drawEllipse(50, 50, 30, 30); // 画圆
}
};
23. QPainter 绘图
void MyWidget::paintEvent(QPaintEvent *) {
QPainter painter(this);
// 设置画笔(线条)
QPen pen(Qt::red, 2, Qt::SolidLine); // 红色、2像素、实线
painter.setPen(pen);
// 设置画刷(填充)
QBrush brush(Qt::blue, Qt::SolidPattern);
painter.setBrush(brush);
// 画各种形状
painter.drawLine(10, 10, 100, 100); // 线
painter.drawRect(10, 10, 80, 60); // 矩形
painter.drawEllipse(10, 10, 80, 60); // 椭圆
painter.drawRoundedRect(10, 10, 80, 60, 10, 10); // 圆角矩形
painter.drawText(10, 30, "Hello"); // 文字
painter.drawImage(10, 10, QImage("logo.png")); // 图片
// 坐标变换
painter.translate(100, 100); // 平移原点
painter.rotate(45); // 旋转45度
painter.scale(2, 2); // 放大2倍
}
24. QPainterPath(复杂路径)
QPainterPath path;
path.moveTo(0, 0); // 起点
path.lineTo(100, 0); // 画线到
path.lineTo(100, 100);
path.lineTo(0, 100);
path.closeSubpath(); // 闭合
path.addEllipse(50, 50, 40, 40); // 加一个圆
painter.drawPath(path);
painter.fillPath(path, QBrush(Qt::blue));
四、事件系统
25. 事件循环
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
// a.exec() 就是事件循环
// 无限循环,不断从系统消息队列取事件,分发给对应的对象
return a.exec();
}
事件处理流程:
用户操作(点击/按键/鼠标移动)
↓
操作系统产生消息
↓
QApplication::exec() 取出消息
↓
QApplication::notify() 分发
↓
QWidget::event() 接收
↓
具体处理函数(mousePressEvent/keyPressEvent)
26. 重写事件处理函数
class MyWidget : public QWidget {
protected:
// 鼠标按下
void mousePressEvent(QMouseEvent *event) override {
QPoint pos = event->pos(); // 鼠标位置(相对控件)
QPoint globalPos = event->globalPos(); // 全局位置
Qt::MouseButton btn = event->button(); // 哪个按钮
if (btn == Qt::LeftButton) {
qDebug() << "左键按下在" << pos;
}
}
// 鼠标移动
void mouseMoveEvent(QMouseEvent *event) override {
// 需要先调用 setMouseTracking(true) 才能持续接收
qDebug() << "鼠标移动到" << event->pos();
}
// 鼠标释放
void mouseReleaseEvent(QMouseEvent *event) override {
qDebug() << "鼠标释放";
}
// 鼠标双击
void mouseDoubleClickEvent(QMouseEvent *event) override {
qDebug() << "双击";
}
// 滚轮
void wheelEvent(QWheelEvent *event) override {
int delta = event->angleDelta().y(); // 正值=上滚,负值=下滚
qDebug() << "滚轮:" << delta;
}
// 键盘按下
void keyPressEvent(QKeyEvent *event) override {
int key = event->key(); // 按键代码
QString text = event->text(); // 按键字符
Qt::KeyboardModifiers mods = event->modifiers(); // 修饰键
if (key == Qt::Key_Escape) {
close();
}
if (mods & Qt::ControlModifier && key == Qt::Key_S) {
save(); // Ctrl+S
}
}
// 键盘释放
void keyReleaseEvent(QKeyEvent *event) override {}
// 窗口大小改变
void resizeEvent(QResizeEvent *event) override {
QSize oldSize = event->oldSize();
QSize newSize = event->size();
qDebug() << "大小从" << oldSize << "变到" << newSize;
}
// 窗口关闭
void closeEvent(QCloseEvent *event) override {
int ret = QMessageBox::question(this, "确认", "确定退出?");
if (ret == QMessageBox::Yes) {
event->accept(); // 允许关闭
} else {
event->ignore(); // 取消关闭
}
}
// 窗口显示/隐藏
void showEvent(QShowEvent *event) override {
qDebug() << "窗口显示了";
}
void hideEvent(QHideEvent *event) override {
qDebug() << "窗口隐藏了";
}
// 焦点变化
void focusInEvent(QFocusEvent *event) override {
qDebug() << "获得焦点";
}
void focusOutEvent(QFocusEvent *event) override {
qDebug() << "失去焦点";
}
// 拖放
void dragEnterEvent(QDragEnterEvent *event) override {
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction(); // 接受拖放
}
}
void dropEvent(QDropEvent *event) override {
QList<QUrl> urls = event->mimeData()->urls();
for (const QUrl &url : urls) {
qDebug() << "拖入文件:" << url.toLocalFile();
}
}
// 绘图(最重要)
void paintEvent(QPaintEvent *event) override {
QPainter painter(this);
// ... 绘图代码
}
};
27. 定时器事件(另一种定时器方式)
class MyWidget : public QWidget {
int mTimerId;
void showEvent(QShowEvent *) override {
mTimerId = startTimer(1000); // 每1000ms触发一次
}
void timerEvent(QTimerEvent *event) override {
if (event->timerId() == mTimerId) {
qDebug() << "定时触发";
}
}
void hideEvent(QHideEvent *) override {
killTimer(mTimerId); // 停止定时器
}
};
和 QTimer 的区别:
| QTimer | timerEvent | |
|---|---|---|
| 使用方式 | 对象+信号槽 | 重写虚函数 |
| 适合场景 | 通用 | 同一对象需要多个定时器时 |
| 灵活性 | 高(可连接lambda) | 低(只能用timerId区分) |
五、文件和IO
28. QFile 详细
// 读文件
QFile file("data.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "打开失败:" << file.errorString();
return;
}
QByteArray data = file.readAll(); // 读全部
// 或逐行读
while (!file.atEnd()) {
QByteArray line = file.readLine();
qDebug() << line.trimmed();
}
file.close();
// 写文件
QFile file("output.txt");
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
file.write("Hello\n");
file.write("World\n");
file.close();
}
// 追加写入
QFile file("log.txt");
if (file.open(QIODevice::Append | QIODevice::Text)) {
file.write("追加的内容\n");
file.close();
}
// 二进制读写
QFile file("data.bin");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
out << (qint32)42 << (double)3.14 << QString("hello");
file.close();
29. QTextStream / QDataStream
// QTextStream:文本读写,自动处理编码
QFile file("data.txt");
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream out(&file);
out.setCodec("UTF-8"); // 设置编码
out << "你好" << Qt::endl;
out << "速度:" << 120.5 << "km/h" << Qt::endl;
file.close();
// 读取
file.open(QIODevice::ReadOnly | QIODevice::Text);
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
}
file.close();
// QDataStream:二进制读写,跨平台字节序
QFile file("data.bin");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
out.setVersion(QDataStream::Qt_5_15); // 指定版本,保证兼容性
out << (quint16)12345 << QString("test");
file.close();
30. QDir 详细
QDir dir;
dir.exists(); // 当前目录是否存在
dir.mkpath("config/logs"); // 递归创建目录
dir.mkdir("newFolder"); // 创建单层目录
dir.remove("file.txt"); // 删除文件
dir.rename("old.txt", "new.txt"); // 重命名
dir.cd("subfolder"); // 进入子目录
dir.cdUp(); // 返回上层目录
dir.absolutePath(); // 绝对路径
dir.entryList(); // 所有文件和目录名
dir.entryList(QDir::Files); // 只要文件
dir.entryList(QDir::Dirs); // 只要目录
dir.entryList({"*.cpp", "*.h"}); // 过滤后缀
dir.entryInfoList(); // 返回 QFileInfoList
// 递归遍历
QDirIterator it("src", {"*.cpp"}, QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
qDebug() << it.next(); // 输出所有子目录下的cpp文件
}
31. QFileInfo
QFileInfo info("/path/to/file.txt");
info.exists(); // true
info.fileName(); // "file.txt"
info.baseName(); // "file" (不含后缀)
info.suffix(); // "txt" (后缀)
info.completeSuffix(); // 后缀(如 "tar.gz" 返回 "tar.gz")
info.absolutePath(); // "/path/to"
info.absoluteFilePath(); // "/path/to/file.txt"
info.size(); // 文件大小(字节)
info.birthTime(); // 创建时间
info.lastModified(); // 最后修改时间
info.isDir(); // false
info.isFile(); // true
info.isExecutable(); // 是否可执行
info.isReadable(); // 是否可读
info.isWritable(); // 是否可写
32. QTemporaryFile / QTemporaryDir
// 临时文件(程序退出自动删除)
QTemporaryFile file;
if (file.open()) {
file.write("临时数据");
QString path = file.fileName(); // 临时文件路径
}
// file析构时自动删除
// 指定前缀和后缀
QTemporaryFile file("report_XXXXXX.xlsx"); // XXXXXX会被替换为随机字符
33. QStandardPaths(标准路径)
QString home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
// C:/Users/用户名
QString desktop = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
// C:/Users/用户名/Desktop
QString appData = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
// C:/Users/用户名/AppData/Roaming/应用名
QString temp = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
// 临时目录
QString doc = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
// C:/Users/用户名/Documents
六、网络
34. QTcpSocket / QTcpServer
// TCP服务端
class Server : public QObject {
QTcpServer *mServer;
public:
Server() {
mServer = new QTcpServer(this);
connect(mServer, &QTcpServer::newConnection, this, &Server::onNewConnection);
mServer->listen(QHostAddress::Any, 8080);
}
void onNewConnection() {
QTcpSocket *socket = mServer->nextPendingConnection();
connect(socket, &QTcpSocket::readyRead, this, [socket]() {
QByteArray data = socket->readAll();
socket->write("收到: " + data);
});
connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
}
};
// TCP客户端
QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost("192.168.0.1", 8080);
connect(socket, &QTcpSocket::connected, this, []() {
qDebug() << "连接成功";
});
connect(socket, &QTcpSocket::readyRead, this, [socket]() {
QByteArray data = socket->readAll();
qDebug() << "收到:" << data;
});
socket->write("Hello Server");
35. QUdpSocket
QUdpSocket *socket = new QUdpSocket(this);
socket->bind(QHostAddress::Any, 17224); // 绑定端口
connect(socket, &QUdpSocket::readyRead, this, [socket]() {
while (socket->hasPendingDatagrams()) {
QByteArray data(socket->pendingDatagramSize(), 0);
QHostAddress sender;
quint16 senderPort;
socket->readDatagram(data.data(), data.size(), &sender, &senderPort);
qDebug() << "收到" << sender.toString() << senderPort << data;
}
});
// 发送
QByteArray data = "Hello";
socket->writeDatagram(data, QHostAddress("239.255.0.130"), 17224);
// 加入组播组
socket->joinMulticastGroup(QHostAddress("239.255.0.130"));
36. QNetworkAccessManager(HTTP)
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
// GET请求
QNetworkReply *reply = manager->get(QNetworkRequest(QUrl("https://api.example.com/data")));
connect(reply, &QNetworkReply::finished, this, [reply]() {
QByteArray data = reply->readAll();
qDebug() << "响应:" << data;
reply->deleteLater();
});
// POST请求
QNetworkRequest request(QUrl("https://api.example.com/login"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QByteArray body = R"({"user":"admin","pass":"123"})";
QNetworkReply *reply = manager->post(request, body);
// 下载文件
QNetworkReply *reply = manager->get(QNetworkRequest(QUrl("https://example.com/file.zip")));
QFile *file = new QFile("file.zip");
file->open(QIODevice::WriteOnly);
connect(reply, &QNetworkReply::readyRead, this, [reply, file]() {
file->write(reply->readAll());
});
connect(reply, &QNetworkReply::finished, this, [reply, file]() {
file->close();
file->deleteLater();
reply->deleteLater();
});
七、数据库
37. SQLite
// 打开数据库
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("app.db");
if (!db.open()) {
qDebug() << "打开失败:" << db.lastError().text();
}
// 执行SQL
QSqlQuery query;
query.exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");
// 插入
query.prepare("INSERT INTO users (name, age) VALUES (?, ?)");
query.addBindValue("张三");
query.addBindValue(25);
query.exec();
// 或用exec直接执行
query.exec("INSERT INTO users (name, age) VALUES ('李四', 30)");
// 查询
query.exec("SELECT id, name, age FROM users");
while (query.next()) {
int id = query.value(0).toInt();
QString name = query.value(1).toString();
int age = query.value(2).toInt();
qDebug() << id << name << age;
}
// 参数化查询(防SQL注入)
query.prepare("SELECT * FROM users WHERE name = :name AND age > :age");
query.bindValue(":name", "张三");
query.bindValue(":age", 20);
query.exec();
38. QSqlTableModel(表格模型)
QSqlTableModel *model = new QSqlTableModel(this, db);
model->setTable("users");
model->select(); // 加载数据
// 绑定到表格视图
tableView->setModel(model);
// 修改后自动提交到数据库
model->setEditStrategy(QSqlTableModel::OnFieldChange);
// 过滤
model->setFilter("age > 20");
model->setSort(1, Qt::DescendingOrder); // 按第1列降序
八、多线程深入
39. QThreadPool + QRunnable
class MyTask : public QRunnable {
void run() override {
// 在线程池里执行
qDebug() << "任务执行中" << QThread::currentThread();
}
};
// 使用
MyTask *task = new MyTask();
task->setAutoDelete(true); // 执行完自动删除
QThreadPool::globalInstance()->start(task);
// 设置线程池最大线程数
QThreadPool::globalInstance()->setMaxThreadCount(4);
和 QThread 的区别:
| QThread | QThreadPool + QRunnable | |
|---|---|---|
| 线程数量 | 手动创建,一个任务一个线程 | 线程池自动管理,复用线程 |
| 适合场景 | 长期运行的任务(TRDP轮询) | 大量短期任务(批量处理) |
40. QtConcurrent(简洁的并行计算)
#include <QtConcurrent>
// 在另一个线程执行函数
QFuture<void> future = QtConrun::run([]() {
// 耗时计算
int sum = 0;
for (int i = 0; i < 1000000; i++) sum += i;
qDebug() << "结果:" << sum;
});
// 并行处理列表
QList<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
QFuture<void> future = QtConcurrent::map(numbers, [](int &n) {
n = n * n; // 每个元素平方
});
// 并行映射(有返回值)
QStringList files = {"a.txt", "b.txt", "c.txt"};
QFuture<QString> future = QtConcurrent::mapped(files, [](const QString &file) {
QFile f(file);
f.open(QIODevice::ReadOnly);
return f.readAll();
});
// 等待结果
future.waitForFinished();
QList<QString> results = future.results();
41. QFuture / QFutureWatcher
QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);
// 监听进度
connect(watcher, &QFutureWatcher<void::progressValueChanged, this, [watcher](int value) {
progressBar->setValue(value);
});
// 监听完成
connect(watcher, &QFutureWatcher<void>::finished, this, []() {
qDebug() << "全部完成";
});
// 启动并行任务
QFuture<void> future = QtConcurrent::run([]() {
// 耗时操作
});
watcher->setFuture(future);
42. 线程间通信方式汇总
// 方式1:信号槽(最常用)
connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::QueuedConnection);
// 方式2:invokeMethod
QMetaObject::invokeMethod(worker, "doWork", Qt::QueuedConnection);
// 方式3:自定义事件
QCoreApplication::postEvent(receiver, new MyEvent());
// 方式4:共享内存
QSharedMemory sharedMem("MySharedMemory");
sharedMem.create(1024); // 创建1024字节共享内存
sharedMem.lock();
memcpy(sharedMem.data(), "hello", 5);
sharedMem.unlock();
九、QML 深入
43. QML 基本类型
property int count: 10
property real price: 9.99 // 浮点数
property string name: "hello"
property bool visible: true
property url link: "https://example.com"
property color bg: "#333333"
property date birthday: "2026-05-27"
property var anything: null // 万能类型
property var list: [1, 2, 3] // 数组
property var obj: ({a: 1, b: 2}) // 对象
44. 锚点布局(Anchors)
Rectangle {
id: parent
width: 400; height: 300
Rectangle {
id: child
width: 100; height: 50
// 锚点布局
anchors.centerIn: parent // 居中
anchors.top: parent.top // 顶部对齐
anchors.left: parent.left // 左边对齐
anchors.right: parent.right // 右边对齐
anchors.bottom: parent.bottom // 底部对齐
anchors.fill: parent // 填满父容器(等同于上面四个全设)
anchors.margins: 10 // 四周边距10
anchors.topMargin: 20 // 只设置上边距
anchors.horizontalCenter: parent.horizontalCenter // 水平居中
anchors.verticalCenter: parent.verticalCenter // 垂直居中
// 相对其他控件定位
anchors.top: otherItem.bottom // 我的顶部 = 另一个的底部(放在它下面)
anchors.left: otherItem.right // 我的左边 = 另一个的右边(放在它右边)
anchors.topMargin: 8 // 间距8
}
}
45. 动画(Animation)
// 属性动画
Rectangle {
id: rect
width: 100; height: 100; color: "red"
// 点击后宽度变200,带动画
MouseArea {
anchors.fill: parent
onClicked: rect.width = 200
}
Behavior on width {
NumberAnimation { duration: 300; easing.type: Easing.InOutQuad }
}
}
// 显式动画
NumberAnimation {
id: anim
target: rect
property: "x"
from: 0; to: 200
duration: 500
easing.type: Easing.OutBounce
}
// 触发
onClicked: anim.start()
// 顺序动画
SequentialAnimation {
NumberAnimation { target: rect; property: "x"; to: 200; duration: 300 }
NumberAnimation { target: rect; property: "y"; to: 200; duration: 300 }
ColorAnimation { target: rect; property: "color"; to: "blue"; duration: 200 }
}
// 并行动画
ParallelAnimation {
NumberAnimation { target: rect; property: "x"; to: 200; duration: 300 }
NumberAnimation { target: rect; property: "y"; to: 200; duration: 300 }
}
// 循环动画
SequentialAnimation {
loops: Animation.Infinite
NumberAnimation { target: rect; property: "opacity"; to: 0; duration: 500 }
NumberAnimation { target: rect; property: "opacity"; to: 1; duration: 500 }
}
46. 状态(States)和过渡(Transitions)
Rectangle {
id: rect
width: 100; height: 100; color: "red"
states: [
State {
name: "expanded"
PropertyChanges { target: rect; width: 300; color: "blue" }
},
State {
name: "normal"
PropertyChanges { target: rect; width: 100; color: "red" }
}
]
transitions: Transition {
NumberAnimation { properties: "width"; duration: 300 }
ColorAnimation { duration: 300 }
}
MouseArea {
anchors.fill: parent
onClicked: rect.state = rect.state === "expanded" ? "normal" : "expanded"
}
}
47. 列表视图(ListView)
ListView {
width: 200; height: 300
model: myModel // 数据源
delegate: Rectangle { // 每一项怎么显示
width: parent.width
height: 40
color: index % 2 === 0 ? "#222" : "#333"
Text {
text: model.display // model.xxx 取数据
color: "white"
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: console.log("点击了第" + index + "项")
}
}
highlight: Rectangle { color: "#1f7a63" } // 选中项高亮
currentIndex: 0 // 默认选中第0项
}
48. Repeater(简单列表)
Column {
Repeater {
model: ["IP地址", "端口", "协议"]
delegate: Text {
text: modelData // modelData 取当前项
color: "white"
}
}
}
49. Timer(QML定时器)
Timer {
id: timer
interval: 1000 // 1000ms
repeat: true // 重复触发
running: false // 初始不运行
onTriggered: {
console.log("每秒触发一次")
}
}
// 启动
onClicked: timer.start()
// 停止
onClicked: timer.stop()
// 一次性定时器
Timer {
interval: 2000
running: true
onTriggered: console.log("2秒后执行一次")
}
50. Connections 监听任意对象信号
Connections {
target: someObject // 监听谁
enabled: root.visible // 只在可见时监听
function onDataReady(data) { // on + 信号名(首字母大写)
console.log(data)
}
function onErrorOccurred(msg) {
errorLabel.text = msg
}
}
51. QML 里调用 JavaScript
// 内联函数
function calculate(a, b) {
return a + b
}
// 调用
onClicked: {
var result = calculate(3, 5)
console.log(result) // 8
}
// 外部JS文件
// utils.js
function formatTime(date) {
return Qt.formatDateTime(date, "hh:mm:ss")
}
// QML里引用
import "utils.js" as Utils
onClicked: console.log(Utils.formatTime(new Date()))
52. Qt Quick Controls 2 控件
import QtQuick.Controls 2.12
Button { text: "按钮" }
ToolButton { icon.source: "qrc:/icon.png" }
RoundButton { text: "+" }
TextField { placeholderText: "输入文字" }
TextArea { text: "多行文本" }
SpinBox { from: 0; to: 100; value: 50 }
Slider { from: 0; to: 100; value: 50 }
Switch { checked: true }
CheckBox { text: "选项"; checked: false }
RadioButton { text: "选项A"; checked: true }
ComboBox { model: ["A", "B", "C"] }
ProgressBar { value: 0.5 }
BusyIndicator { running: true } // 转圈加载指示器
Menu {
title: "文件"
Action { text: "新建"; onTriggered: console.log("新建") }
Action { text: "打开"; onTriggered: console.log("打开") }
MenuSeparator {}
Action { text: "退出"; onTriggered: Qt.quit() }
}
Dialog {
title: "确认"
standardButtons: Dialog.Ok | Dialog.Cancel
Label { text: "确定要退出吗?" }
onAccepted: Qt.quit()
}
SwipeView {
currentIndex: 0
Page { Label { text: "第一页" } }
Page { Label { text: "第二页" } }
}
Drawer {
edge: Qt.LeftEdge
width: 200; height: parent.height
Label { text: "侧边栏" }
}
十、高级特性
53. 插件系统
// 定义接口
class IPlugin {
public:
virtual ~IPlugin() {}
virtual QString name() = 0;
virtual void execute() = 0;
};
Q_DECLARE_INTERFACE(IPlugin, "com.myapp.IPlugin/1.0")
// 实现插件
class MyPlugin : public QObject, public IPlugin {
Q_OBJECT
Q_PLUGIN_METADATA(IID "com.myapp.IPlugin/1.0")
Q_INTERFACES(IPlugin)
public:
QString name() override { return "MyPlugin"; }
void execute() override { qDebug() << "插件执行"; }
};
// 加载插件
QDir dir("plugins");
for (const QString &fileName : dir.entryList({"*.dll", "*.so"})) {
QPluginLoader loader(dir.absoluteFilePath(fileName));
QObject *plugin = loader.instance();
if (plugin) {
IPlugin *iface = qobject_cast<IPlugin*>(plugin);
if (iface) {
iface->execute();
}
}
}
54. 国际化(i18n)
// C++代码里
QLabel *label = new QLabel(tr("你好")); // tr() 标记需要翻译的字符串
qDebug() << tr("当前版本: %1").arg(version);
// QML代码里
Label { text: qsTr("你好") }
Label { text: qsTr("共 %1 条记录").arg(count) }
// 加载翻译文件
QTranslator translator;
if (translator.load("app_zh_CN.qm")) {
qApp->installTranslator(&translator);
}
// 工具流程
// 1. lupdate 提取所有 tr() 字符串 → 生成 .ts 文件
// 2. Linguist 翻译 .ts 文件
// 3. lrelease 编译 .ts → .qm 文件
55. QLoggingCategory(日志分类)
// 定义分类
Q_LOGGING_CATEGORY(networkLog, "app.network")
Q_LOGGING_CATEGORY(uiLog, "app.ui")
// 使用
qCDebug(networkLog) << "网络连接成功";
qCWarning(uiLog) << "界面异常";
// 运行时设置哪些分类输出
QLoggingCategory::setFilterRules("app.network.debug=true\napp.ui.debug=false");
56. Q_ENUM / Q_FLAG(枚举注册)
class MyClass : public QObject {
Q_OBJECT
public:
enum Status { Idle, Running, Error };
Q_ENUM(Status) // 注册到元对象系统
enum Flags { Read = 1, Write = 2, Execute = 4 };
Q_DECLARE_FLAGS(Permissions, Flags)
Q_FLAG(Permissions)
};
// 现在可以
MyClass::Status s = MyClass::Running;
QMetaEnum metaEnum = QMetaEnum::fromType<MyClass::Status>();
metaEnum.valueToKey(s); // "Running"
metaEnum.keyToValue("Error"); // 2
57. QCommandLineParser(命令行参数)
QCommandLineParser parser;
parser.setApplicationDescription("TRDP工作台");
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption configOpt("c", "配置文件", "文件名", "config.ini");
parser.addOption(configOpt);
parser.process(app);
QString configFile = parser.value(configOpt); // "config.ini"
// 命令行: TrdpModel.exe -c config-beijing.ini
58. QProcess(启动外部程序)
QProcess *process = new QProcess(this);
// 启动并等待完成
process->start("cmd", {"/c", "dir"});
process->waitForFinished();
QString output = process->readAllStandardOutput();
QString error = process->readAllStandardError();
// 异步启动
connect(process, &QProcess::readyReadStandardOutput, this, [process]() {
qDebug() << process->readAllStandardOutput();
});
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [](int exitCode, QProcess::ExitStatus status) {
qDebug() << "退出码:" << exitCode;
});
process->start("python", {"script.py"});
// 启动并忘记(打开文件/URL)
QProcess::startDetached("notepad.exe", {"data.txt"});
QDesktopServices::openUrl(QUrl("https://example.com"));
59. QSerialPort(串口通信)
QSerialPort *serial = new QSerialPort(this);
serial->setPortName("COM3");
serial->setBaudRate(QSerialPort::Baud115200);
serial->setDataBits(QSerialPort::Data8);
serial->setParity(QSerialPort::NoParity);
serial->setStopBits(QSerialPort::OneStop);
serial->setFlowControl(QSerialPort::NoFlowControl);
if (serial->open(QIODevice::ReadWrite)) {
connect(serial, &QSerialPort::readyRead, this, [serial]() {
QByteArray data = serial->readAll();
qDebug() << "收到:" << data.toHex();
});
serial->write(QByteArray::fromHex("01 02 03"));
}
// 列出可用串口
for (const QSerialPortInfo &info : QSerialPortInfo::availablePorts()) {
qDebug() << info.portName() << info.description();
}
60. QTimer 单次和精密定时
// 单次定时器
QTimer::singleShot(1000, this, &MyClass::doSomething);
// 单次定时器(lambda)
QTimer::singleShot(1000, []() { qDebug() << "1秒后执行"; });
// 精密定时器
QTimer *timer = new QTimer(this);
timer->setTimerType(Qt::PreciseTimer); // 精确到毫秒(默认是CoarseTimer,精度约±5%)
timer->setInterval(10); // 10ms
timer->start();
// 精度对比
// Qt::PreciseTimer 精确到毫秒级,CPU占用高
// Qt::CoarseTimer 精度约±5%,省电(默认)
// Qt::VeryCoarseTimer 精度约±5%,最小间隔1秒
61. QElapsedTimer(高精度计时)
QElapsedTimer timer;
timer.start();
// 做一些事情
for (int i = 0; i < 1000000; i++) { /* ... */ }
qint64 elapsed = timer.elapsed(); // 毫秒
qint64 nanoSec = timer.nsecsElapsed(); // 纳秒
qDebug() << "耗时:" << elapsed << "ms";
62. QSharedPointer / QWeakPointer / QScopedPointer
// QSharedPointer:引用计数智能指针
QSharedPointer<MyClass> p1(new MyClass());
QSharedPointer<MyClass> p2 = p1; // 引用计数=2
p1.clear(); // 引用计数=1
p2.clear(); // 引用计数=0,自动delete
// QWeakPointer:弱引用,不影响引用计数
QWeakPointer<MyClass> weak = p1;
QSharedPointer<MyClass> locked = weak.toStrongRef(); // 尝试升级为强引用
if (locked) { /* 还活着 */ }
// QScopedPointer:作用域指针,离开作用域自动释放
{
QScopedPointer<QFile> file(new QFile("data.txt"));
file->open(QIODevice::ReadOnly);
// 离开作用域自动 delete
}
// QScopedArrayPointer:数组版本
QScopedArrayPointer<int> arr(new int[100]);
63. QVariant(万能类型)
QVariant v;
v = 42; // 存int
v = "hello"; // 存字符串
v = QList<QVariant>{1, 2}; // 存列表
v = QVariantMap{{"a", 1}}; // 存字典
v.toInt(); // 42
v.toString(); // "hello"
v.type(); // QVariant::Int / QVariant::String
v.typeName(); // "int" / "QString"
v.canConvert<int>(); // 能不能转成int
// 和 QVariantMap 配合使用
QVariantMap map;
map["name"] = "张三";
map["age"] = 25;
map["scores"] = QVariantList{90, 85, 92};
64. QJsonDocument / QJsonObject / QJsonArray
// 解析JSON
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
QJsonObject obj = doc.object();
QString name = obj["name"].toString();
int age = obj["age"].toInt();
QJsonArray scores = obj["scores"].toArray();
// 构建JSON
QJsonObject obj;
obj["name"] = "张三";
obj["age"] = 25;
QJsonArray arr;
arr.append(90);
arr.append(85);
obj["scores"] = arr;
QJsonDocument doc(obj);
QByteArray json = doc.toJson(QJsonDocument::Indented); // 格式化输出
// {"age": 25, "name": "张三", "scores": [90, 85]}
65. QXmlStreamReader / QXmlStreamWriter
// 读XML
QXmlStreamReader xml(xmlData);
while (!xml.atEnd()) {
xml.readNext();
if (xml.isStartElement()) {
if (xml.name() == "item") {
QString id = xml.attributes().value("id").toString();
qDebug() << "item id=" << id;
}
}
if (xml.isCharacters() && !xml.isWhitespace()) {
qDebug() << "文本:" << xml.text();
}
}
// 写XML
QXmlStreamWriter xml(&output);
xml.setAutoFormatting(true);
xml.writeStartDocument();
xml.writeStartElement("root");
xml.writeStartElement("item");
xml.writeAttribute("id", "1");
xml.writeTextElement("name", "张三");
xml.writeEndElement(); // item
xml.writeEndElement(); // root
xml.writeEndDocument();
66. QRegularExpression(正则表达式)
QRegularExpression re(R"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})");
// 匹配IP地址
QRegularExpressionMatch match = re.match("服务器IP是192.168.0.103");
if (match.hasMatch()) {
QString ip = match.captured(0); // "192.168.0.103"
}
// 全局匹配(找所有)
QRegularExpression re2(R"(\d+)");
QString text = "共123条,已处理45条";
QRegularExpressionMatchIterator it = re2.globalMatch(text);
while (it.hasNext()) {
QRegularExpressionMatch m = it.next();
qDebug() << m.captured(0); // "123" "45"
}
// 替换
QString result = text.replace(re2, "XX");
// "共XX条,已处理XX条"
// 捕获组
QRegularExpression re3(R"((\w+)@(\w+)\.(\w+))");
QRegularExpressionMatch m = re3.match("user@example.com");
m.captured(0); // "user@example.com" 整个匹配
m.captured(1); // "user" 第1个括号
m.captured(2); // "example" 第2个括号
m.captured(3); // "com" 第3个括号
67. QHostAddress / QNetworkInterface
// 解析IP地址
QHostAddress addr("192.168.0.1");
quint32 ipv4 = addr.toIPv4Address(); // 3232235521
// 列出所有网卡和IP
for (const QNetworkInterface &iface : QNetworkInterface::allInterfaces()) {
qDebug() << "网卡:" << iface.humanReadableName();
for (const QNetworkAddressEntry &entry : iface.addressEntries()) {
qDebug() << " IP:" << entry.ip().toString();
qDebug() << " 子网掩码:" << entry.netmask().toString();
}
}
十一、调试和工具
68. qDebug / qInfo / qWarning / qCritical / qFatal
qDebug() << "调试信息"; // 默认输出到控制台
qInfo() << "普通信息";
qWarning() << "警告";
qCritical() << "严重错误";
qFatal() << "致命错误"; // 输出后程序终止
// 自定义格式
qSetMessagePattern("[%{type}] %{file}:%{line} - %{message}");
// 输出: [Debug] main.cpp:10 - 调试信息
69. 自定义消息处理器
void myMessageHandler(QtMsgType type, const QMessageLogContext &ctx, const QString &msg) {
QFile file("log.txt");
file.open(QIODevice::Append | QIODevice::Text);
QTextStream out(&file);
out << QDateTime::currentDateTime().toString("hh:mm:ss.zzz") << " ";
out << msg << "\n";
}
int main(int argc, char *argv[]) {
qInstallMessageHandler(myMessageHandler); // 安装自定义处理器
// 之后所有 qDebug/qWarning 等都走这个函数
}
70. 断言和测试
// 断言
Q_ASSERT(x > 0); // 失败→崩溃+打印位置
Q_ASSERT_X(list.size() > 0, "func", "空列表"); // 失败→崩溃+自定义消息
// 单元测试
#include <QtTest/QTest>
class TestMyClass : public QObject {
Q_OBJECT
private slots:
void testAdd() {
QCOMPARE(1 + 1, 2); // 相等
QVERIFY(2 > 1); // 为真
QVERIFY2(list.size() > 0, "列表为空"); // 为真+失败消息
QEXPECT_FAIL("", "已知bug", Continue); // 预期失败
QCOMPARE(actual, expected);
}
void testInit() {
// 每个slot是一个测试用例
}
};
QTEST_MAIN(TestMyClass) // 生成main函数
#include "test.moc"
71. 性能分析
// 方法1:QElapsedTimer
QElapsedTimer timer;
timer.start();
// ... 要测的代码
qDebug() << "耗时:" << timer.elapsed() << "ms";
// 方法2:QScopedProfiler(Qt6)
QScopedProfiler p("MyOperation");
// ... 要测的代码
// 析构时自动输出耗时
// 方法3:QLatin1String vs QStringLiteral
// QLatin1String:编译期,零拷贝,适合ASCII比较
if (str == QLatin1String("config")) { /* 快 */ }
// QStringLiteral:编译期创建QString,避免运行时转换
if (str == QStringLiteral("config")) { /* 也快 */ }
// 直接写 "config":运行时从const char*转换,慢
if (str == "config") { /* 慢 */ }
十二、Qt 的坑和最佳实践
72. 常见坑
// 坑1:信号槽连接后对象被删除
MyClass *obj = new MyClass();
connect(sender, &Sender::signal, obj, &MyClass::slot);
delete obj;
// obj被删除后,连接自动断开,不会崩溃
// 但如果用lambda捕获了裸指针,可能会崩溃
connect(sender, &Sender::signal, this, [obj]() {
obj->doSomething(); // 危险:obj可能已被删除
});
// 解决:用 QPointer 或 weak_ptr
// 坑2:QueuedConnection下参数生命周期
void send() {
QByteArray data(1000, 'x');
emit dataReady(data);
// data在这里被销毁
// 如果是QueuedConnection,Qt会自动拷贝data,安全
// 如果是DirectConnection,不涉及拷贝,也安全
}
// 坑3:Q_OBJECT漏加
// 不加Q_OBJECT:
// - 信号槽连接失败(运行时)
// - dynamic_cast 失败
// - qobject_cast 返回nullptr
// 坑4:事件循环阻塞
void MyWidget::onButtonClick() {
QThread::sleep(5); // 界面卡死5秒!
}
// 解决:耗时操作放到子线程
// 坑5:QThread对象本身在主线程
QThread *thread = new QThread();
// thread的所有槽函数在主线程执行(除非moveToThread)
// thread->start()只是启动了run(),thread对象还在主线程
73. 最佳实践
// 1. 用智能指针管理内存
QScopedPointer<QFile> file(new QFile("data.txt"));
// 2. 用QPointer跟踪QObject生命周期
QPointer<MyClass> safePtr = obj;
if (safePtr) { // 检查对象是否还活着
safePtr->doSomething();
}
// 3. 用deleteLater代替delete
obj->deleteLater(); // 等事件循环处理完再删除,比直接delete安全
// 4. 用QLatin1String做字符串比较
if (key == QLatin1String("config")) { /* 快 */ }
// 5. 用qobject_cast代替dynamic_cast
MyClass *c = qobject_cast<MyClass*>(obj); // 失败返回nullptr,不用RTTI
// 6. 用const引用传参
void process(const QByteArray &data); // 不拷贝
// 7. 用QByteArray::fromHex做十六进制转换,不要自己写
QByteArray data = QByteArray::fromHex("FF AB 01"); // 正确
// 不要用 data = "\xFF\xAB\x01"(不可移植)
// 8. 配置文件路径用QCoreApplication::applicationDirPath()
QString path = QCoreApplication::applicationDirPath() + "/config";
// 不要用 QDir::currentPath()(工作目录可能变)
74. Qt5 vs Qt6 主要区别
Qt5 Qt6
────────────────────────────────────────────────────
#include <QApplication> #include <QApplication> (一样)
QT += widgets QT += widgets (一样)
QRegExp QRegularExpression (Qt6移除QRegExp)
QTextCodec 自动UTF-8,不需要了
QDesktopWidget QScreen
QApplication::desktop() QGuiApplication::primaryScreen()
Qt::MidButton Qt::MiddleButton
QVariant::Type QMetaType
qmlRegisterType qmlRegisterType (一样)
QQuickView QQuickView (一样)
setResizeMode setResizeMode (一样)