跳到主要内容

Qt 核心概念

Qt 框架有一些独特的设计理念和核心机制,理解这些概念是掌握 Qt 开发的关键。

元对象系统(Meta-Object System)

Qt 的元对象系统是其最核心的特性之一,它为 C++ 添加了反射、信号槽、属性系统等动态特性。

元对象编译器(moc)

Qt 使用 moc(Meta-Object Compiler)在编译时处理特殊的 C++ 代码:

class MyClass : public QObject
{
Q_OBJECT // 这个宏会触发 moc 处理

public:
MyClass(QObject *parent = nullptr);

signals:
void mySignal(); // 信号声明

public slots:
void mySlot(); // 槽声明
};

编译流程:

头文件 (.h)


moc 编译器 ──► moc_xxx.cpp(生成的元对象代码)


C++ 编译器 ──► 目标文件


链接器 ──► 可执行文件

Q_OBJECT 宏的作用

Q_OBJECT 宏展开后包含:

  • static const QMetaObject staticMetaObject - 类的元对象
  • virtual const QMetaObject *metaObject() const - 获取元对象
  • virtual void *qt_metacast(const char *) - 动态类型转换
  • virtual int qt_metacall(QMetaObject::Call, int, void **) - 调用信号槽

这些功能使得 Qt 能够在运行时获取类信息、调用方法、实现信号槽。

信号与槽(Signals & Slots)

信号槽是 Qt 对象间通信的机制,替代了传统的回调函数。

基本概念

  • 信号(Signal):当某个事件发生时发出的通知
  • 槽(Slot):接收信号并执行相应操作的函数
  • 连接(Connect):将信号和槽关联起来

基本用法

#include <QObject>
#include <QDebug>

class Sender : public QObject
{
Q_OBJECT

signals:
// 信号声明:只需声明,无需实现
void valueChanged(int newValue);
void stringSignal(const QString &text);
};

class Receiver : public QObject
{
Q_OBJECT

public slots:
// 槽函数:普通的成员函数
void onValueChanged(int value)
{
qDebug() << "收到数值:" << value;
}

void onStringReceived(const QString &text)
{
qDebug() << "收到字符串:" << text;
}
};

// 使用
Sender sender;
Receiver receiver;

// 连接信号和槽
QObject::connect(&sender, &Sender::valueChanged,
&receiver, &Receiver::onValueChanged);

// 发射信号
emit sender.valueChanged(42); // 接收者会收到通知

连接类型

Qt 提供多种连接方式,控制槽函数的执行时机:

连接类型说明使用场景
Qt::AutoConnection自动选择(默认)大多数情况
Qt::DirectConnection立即直接调用同线程,需要立即执行
Qt::QueuedConnection放入事件队列异步执行跨线程通信
Qt::BlockingQueuedConnection阻塞等待槽执行完成跨线程,需要结果
Qt::UniqueConnection确保连接唯一防止重复连接
// 指定连接类型
connect(sender, &Sender::signal, receiver, &Receiver::slot,
Qt::QueuedConnection);

Lambda 表达式作为槽

Qt 5 起支持使用 Lambda 表达式,使代码更简洁:

QPushButton *button = new QPushButton("Click");
QLabel *label = new QLabel("Count: 0");
int count = 0;

// Lambda 作为槽
connect(button, &QPushButton::clicked, [=, &count]() mutable {
count++;
label->setText(QString("Count: %1").arg(count));
});

信号连接信号

信号可以连接另一个信号,实现信号转发:

// 窗口内的按钮点击转发为窗口的提交信号
connect(ui->submitButton, &QPushButton::clicked,
this, &MyDialog::submitted);

断开连接

// 断开特定连接
disconnect(sender, &Sender::signal, receiver, &Receiver::slot);

// 断开接收者的所有槽
disconnect(sender, nullptr, receiver, nullptr);

// 断开发送者的所有信号
disconnect(sender, nullptr, nullptr, nullptr);

对象树与内存管理

Qt 使用对象树(Object Tree)实现自动内存管理。

父子对象关系

// 创建父对象
QWidget *window = new QWidget;

// 创建子对象,指定父对象
QPushButton *button1 = new QPushButton("Button 1", window);
QPushButton *button2 = new QPushButton("Button 2", window);
QLabel *label = new QLabel("Label", window);

// 对象树结构:
// window
// ├── button1
// ├── button2
// └── label

// 删除父对象会自动删除所有子对象
delete window; // button1, button2, label 也会被删除

内存管理规则

  1. 有父对象时:父对象销毁时自动销毁子对象,不要手动 delete
  2. 无父对象时:需要手动管理内存,或使用智能指针
  3. QObject 的子类:优先使用对象树管理
  4. 非 QObject 对象:使用标准 C++ 内存管理
// 好的实践:使用对象树
void setupUI(QWidget *parent)
{
QVBoxLayout *layout = new QVBoxLayout(parent);
QPushButton *btn1 = new QPushButton("OK", parent);
QPushButton *btn2 = new QPushButton("Cancel", parent);

layout->addWidget(btn1);
layout->addWidget(btn2);
// 无需手动 delete,parent 销毁时会自动清理
}

// 特殊情况:需要提前释放
void temporaryObject()
{
QDialog *dialog = new QDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose); // 关闭时自动删除
dialog->show();
}

事件系统

Qt 使用事件驱动编程模型,所有用户交互都通过事件处理。

事件循环

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

MainWindow window;
window.show();

// 启动事件循环
// 不断从系统获取事件并分发处理
return app.exec();
}

常见事件类型

事件类触发时机处理方法
QMouseEvent鼠标操作mousePressEvent, mouseMoveEvent
QKeyEvent键盘操作keyPressEvent, keyReleaseEvent
QPaintEvent需要重绘paintEvent
QResizeEvent窗口大小改变resizeEvent
QCloseEvent窗口关闭closeEvent
QTimerEvent定时器触发timerEvent

事件处理示例

class MyWidget : public QWidget
{
public:
MyWidget(QWidget *parent = nullptr) : QWidget(parent) {}

protected:
// 重写鼠标按下事件
void mousePressEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton) {
qDebug() << "左键点击:" << event->pos();
}
// 调用基类实现(保持默认行为)
QWidget::mousePressEvent(event);
}

// 重写键盘事件
void keyPressEvent(QQKeyEvent *event) override
{
switch (event->key()) {
case Qt::Key_Escape:
close();
break;
case Qt::Key_F1:
showHelp();
break;
default:
QWidget::keyPressEvent(event);
}
}

// 重绘事件
void paintEvent(QPaintEvent *event) override
{
QPainter painter(this);
painter.setBrush(Qt::blue);
painter.drawRect(10, 10, 100, 80);
}
};

事件过滤器

事件过滤器允许一个对象监视另一个对象的事件:

class EventFilter : public QObject
{
protected:
bool eventFilter(QObject *watched, QEvent *event) override
{
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
qDebug() << "拦截到鼠标事件:" << mouseEvent->pos();
return true; // 消费事件,不再传递
}
return QObject::eventFilter(watched, event); // 继续传递
}
};

// 使用
EventFilter *filter = new EventFilter(this);
ui->button->installEventFilter(filter); // 为按钮安装过滤器

发送自定义事件

// 定义自定义事件类型
const QEvent::Type MyCustomEventType =
static_cast<QEvent::Type>(QEvent::User + 1);

class MyCustomEvent : public QEvent
{
public:
MyCustomEvent(const QString &data)
: QEvent(MyCustomEventType), m_data(data) {}

QString data() const { return m_data; }

private:
QString m_data;
};

// 发送事件
QCoreApplication::postEvent(receiver, new MyCustomEvent("Hello"));

// 处理自定义事件
void MyObject::customEvent(QEvent *event) override
{
if (event->type() == MyCustomEventType) {
MyCustomEvent *myEvent = static_cast<MyCustomEvent*>(event);
qDebug() << "收到自定义事件:" << myEvent->data();
}
}

属性系统

Qt 的属性系统提供了动态访问对象属性的能力。

定义属性

class Person : public QObject
{
Q_OBJECT
// 声明属性
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)

public:
explicit Person(QObject *parent = nullptr) : QObject(parent) {}

// Getter
QString name() const { return m_name; }
int age() const { return m_age; }

// Setter
void setName(const QString &name) {
if (m_name != name) {
m_name = name;
emit nameChanged(name);
}
}

void setAge(int age) {
if (m_age != age) {
m_age = age;
emit ageChanged(age);
}
}

signals:
void nameChanged(const QString &name);
void ageChanged(int age);

private:
QString m_name;
int m_age = 0;
};

使用属性

Person person;

// 通过 setter/getter
person.setName("张三");
person.setAge(25);
qDebug() << person.name(); // "张三"

// 通过元对象系统动态访问
person.setProperty("name", "李四");
QVariant name = person.property("name");
qDebug() << name.toString(); // "李四"

// 获取所有属性
const QMetaObject *meta = person.metaObject();
for (int i = 0; i < meta->propertyCount(); i++) {
QMetaProperty prop = meta->property(i);
qDebug() << "属性:" << prop.name();
}

字符串与容器

Qt 提供了自己的字符串和容器类,与 C++ STL 类似但更适合 Qt 开发。

QString

Qt 的字符串类,支持 Unicode:

// 创建字符串
QString str1 = "Hello";
QString str2 = QStringLiteral("World"); // 编译期优化

// 字符串操作
QString result = str1 + " " + str2; // "Hello World"
result.append("!");
result.prepend("Say: ");

// 格式化
QString formatted = QString("Name: %1, Age: %2").arg("张三").arg(25);

// 与数字转换
int num = QString("42").toInt();
QString str = QString::number(3.14159, 'f', 2); // "3.14"

// 中文字符串
QString chinese = QStringLiteral("你好,世界");
int length = chinese.length(); // 6(字符数,不是字节数)

Qt 容器类

Qt 容器STL 等价特点
QList<T>std::vector<T>通用列表,推荐首选
QVector<T>std::vector<T>连续内存存储
QLinkedList<T>std::list<T>双向链表
QMap<Key, T>std::map<Key, T>有序字典
QHash<Key, T>std::unordered_map<Key, T>哈希表,查找更快
QSet<T>std::set<T>集合
QStringList-QString 的列表
// QList 示例
QList<int> numbers;
numbers << 1 << 2 << 3; // 流式插入
numbers.append(4);
numbers.prepend(0);

for (int num : numbers) {
qDebug() << num;
}

// 使用 Java 风格迭代器
QListIterator<int> iter(numbers);
while (iter.hasNext()) {
qDebug() << iter.next();
}

// QMap 示例
QMap<QString, int> scores;
scores["张三"] = 90;
scores["李四"] = 85;
scores.insert("王五", 95);

// 遍历
for (auto it = scores.begin(); it != scores.end(); ++it) {
qDebug() << it.key() << ":" << it.value();
}

// C++11 范围 for
for (const auto &[name, score] : scores.toStdMap()) {
qDebug() << name << score;
}

总结

Qt 的核心概念相互关联,形成完整的开发框架:

  1. 元对象系统 - 提供反射、信号槽、属性等动态特性
  2. 信号槽 - 对象间松耦合的通信机制
  3. 对象树 - 自动内存管理
  4. 事件系统 - 响应用户交互和系统事件
  5. 属性系统 - 动态访问对象状态

理解这些概念后,你就能更好地利用 Qt 开发高效、稳定的应用程序。