Qt 核心概念

FreeGuideOnline 最新 2026-07-14

cpp class MyClass : public QObject { Q_OBJECT // 必须的宏 public: explicit MyClass(QObject *parent = nullptr); };


### Q_OBJECT 宏与元对象编译器 (moc)

注意到类声明中的 `Q_OBJECT` 宏了吗?它不是可有可无的装饰。Qt 在标准 C++ 编译之前,会先用**元对象编译器 (Meta-Object Compiler, moc)** 处理头文件,为带有 `Q_OBJECT` 宏的类生成额外的 C++ 代码。这些代码实现了:

-   运行时类型信息(比 C++ RTTI 更详尽)
-   信号与槽机制
-   动态属性系统

**初学者注意**:每次为类添加 `Q_OBJECT` 宏后,都需要重新运行 qmake 或 CMake,确保 moc 重新生成对应实现文件,否则会出现链接错误。

### 对象所有权与对象树

Qt 通过父子关系来管理对象的生命周期。当你用一个 `parent` 参数构造一个 `QObject` 子对象时,该对象会被添加到父对象的“对象树”中。当父对象被销毁时,它会自动销毁所有子对象。

-   **优点**:避免了大量手动 `delete`,极大降低内存泄漏风险。
-   **注意**:如果你在一个栈对象上创建子对象,必须保证栈对象的析构顺序正确,或者在子对象析构前将其解除父子关系。通常推荐创建在堆上并指定 `parent`。

```cpp
QObject *parent = new QObject;
QObject *child  = new QObject(parent);
delete parent; // child 会被自动析构

二、信号与槽 —— 对象间通信的灵魂

什么是信号与槽

信号与槽是 Qt 最核心的通信机制,用于替代传统的回调函数。它实现了松散耦合的对象间通信:一个对象发出信号,连接到此信号的一个或多个槽函数会被自动调用,而双方彼此不必知道对方的任何具体实现细节。

  • 信号 (signal):在特定事件发生时被发射的函数声明,由 moc 生成实现。
  • 槽 (slot):普通的 C++ 成员函数,可以经由信号调用,也能像一般函数般直接调用。

基本连接语法

在 Qt5 及以后,推荐使用新式信号槽语法,它具备编译期类型检查,避免了旧式基于字符串语法的运行时风险。

// 发送者:QPushButton
// 信号:   clicked()
// 接收者:QApplication
// 槽:     quit()
QObject::connect(button, &QPushButton::clicked,
                 qApp,   &QApplication::quit);

当按钮被点击时,应用程序退出。连接可以跨线程、跨对象,乃至连接到 lambda 表达式。

一个完整示例

class Sender : public QObject
{
    Q_OBJECT
public:
    void doWork() { emit valueChanged(42); }
signals:
    void valueChanged(int newValue);
};

class Receiver : public QObject
{
    Q_OBJECT
public slots:
    void onValueChanged(int value) {
        qDebug() << "Received:" << value;
    }
};

// 使用
auto sender   = new Sender;
auto receiver = new Receiver;
QObject::connect(sender, &Sender::valueChanged,
                 receiver, &Receiver::onValueChanged);
sender->doWork();  // 输出 "Received: 42"

信号与槽的连接类型

默认情况下,连接类型为 Qt::AutoConnection

  • 若发送者和接收者在同一线程,则为直接连接(槽立即执行,类似同步调用)。
  • 若在不同线程,则为队列连接(信号被放入接收者线程的事件队列,异步执行)。

你也可以显式指定 Qt::QueuedConnectionQt::BlockingQueuedConnection(跨线程同步调用,仅在接收者线程不同于发送者线程时有效)。

三、事件系统与事件循环

事件驱动模型

Qt 应用程序是事件驱动的。当一个 Qt 程序启动后,QApplication::exec() 会启动主事件循环。事件循环持续从事件队列中取出事件,并分发到对应对象的事件处理函数中。

常见的事件类型包括:

  • 用户输入(鼠标、键盘)
  • 定时器事件 QTimerEvent
  • 重绘事件 QPaintEvent
  • 窗口状态变化事件

事件处理与重载

每个 QObject 都可以通过重写 event() 函数或具体的事件处理器来处理事件。例如,要让一个自定义 Widget 响应键盘按下,你可以重写 keyPressEvent(QKeyEvent *event)

class MyWidget : public QWidget
{
protected:
    void keyPressEvent(QKeyEvent *event) override {
        if (event->key() == Qt::Key_Escape)
            close();
        else
            QWidget::keyPressEvent(event); // 确保未处理的事件继续传播
    }
};

事件过滤器

如果不想派生类,可以使用事件过滤器。调用 installEventFilter() 后,接收对象会首先将发送对象的事件交给过滤器的 eventFilter() 方法处理。

class Filter : public QObject {
protected:
    bool eventFilter(QObject *obj, QEvent *event) override {
        if (event->type() == QEvent::KeyPress) {
            // 处理按键事件
            return true; // 事件已消费,不再传递
        }
        return QObject::eventFilter(obj, event);
    }
};

事件系统是比信号槽更底层的机制,一般只有在你需要精确控制事件流时才会直接使用。

四、布局管理 —— 自适应界面的基石

为何需要布局

手动设置坐标和大小来排列控件,无法应对窗口缩放或不同分辨率。Qt 的布局系统通过布局管理器自动调整子控件的位置和尺寸,保证界面始终美观。

常见的布局类

  • QHBoxLayout:水平排列控件
  • QVBoxLayout:垂直排列控件
  • QGridLayout:网格布局,类似表格
  • QFormLayout:标签-字段对布局,适合设置界面

使用示例

QWidget *window = new QWidget;
QPushButton *btn1 = new QPushButton("One");
QPushButton *btn2 = new QPushButton("Two");
QPushButton *btn3 = new QPushButton("Three");

QVBoxLayout *layout = new QVBoxLayout(window);
layout->addWidget(btn1);
layout->addWidget(btn2);
layout->addWidget(btn3);
window->setLayout(layout);
window->show();

布局可以嵌套,如一个水平布局内可以放置一个垂直布局,以实现复杂界面结构。

尺寸策略与伸缩因子

每个 QWidget 都有 sizePolicy 属性,决定其在布局中的缩放行为。拉伸因子 (stretch factor) 则允许控件按比例占用额外空间。

layout->addWidget(btn1, 1); // 拉伸因子 1
layout->addWidget(btn2, 2); // 拉伸因子 2,将获得 btn1 两倍的额外空间

五、常用核心类简介

字符串:QString

QString 是 Qt 对 Unicode 字符串的封装,支持深拷贝、写时复制和丰富的操作函数。建议在所有 Qt 代码中统一使用 QString 而非 std::string,以获得最佳集成体验。

QString s = "Hello";
s.append(" Qt");
qDebug() << s.toUpper();   // "HELLO QT"

容器类

Qt 提供了类似 STL 但更轻量且支持隐式共享(写时复制)的容器,如 QListQVectorQMapQHash 等。对于大量数据或频繁访问的场景,QVector 通常比 QList 更高效。

QVector<int> numbers = {1, 2, 3};
numbers.append(4);
for (int n : numbers)
    qDebug() << n;

文件与流

QFileQDataStreamQTextStream 配合,使文件 I/O 非常直观。

QFile file("output.txt");
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
    QTextStream stream(&file);
    stream << "Qt Core Concepts\n";
}