3、C++基础回顾与Qt扩展:C++核心语法回顾、Qt对C++的扩展(信号与槽、元对象系统)、Qt容器与算法

说实话,很多同学学Qt学得痛苦,根源不在Qt本身,而是C++基础没打牢。我见过太多人,信号与槽写得很溜,但一问到虚函数、智能指针就懵了。嗯,这其实很正常——Qt对C++做了很多封装,让你感觉不到底层细节,但一旦遇到复杂问题,基础不牢的代价就来了。

这一章,咱们不搞「从零学C++」那套。我挑几个最核心、最常用的点,结合我踩过的坑,帮你快速回顾。同时看看Qt到底在C++之上「加」了哪些好东西。

3.1 C++核心语法:你真正需要掌握的

我个人习惯,把C++基础分成三块:内存管理、面向对象、模板与STL。咱们一个一个来。

3.1.1 内存管理:别再new了

我记得刚入行那会儿,写代码全是 new 和 delete。后来项目越做越大,内存泄漏查得我头皮发麻。现在我的原则很简单:能用栈就不用堆,能用智能指针就不用裸指针。

核心原则:

  • 局部对象:直接定义在栈上,生命周期自动管理
  • 需要动态分配:用 std::unique_ptr 或 std::shared_ptr
  • Qt对象:利用Qt的父子对象机制,自动析构
// 不推荐
MyClass* obj = new MyClass();
// ... 忘了 delete

// 推荐(C++11+)
auto obj = std::make_unique<MyClass>();

// Qt风格
MyClass* obj = new MyClass(parent); // parent 负责析构

我的经验:在Qt项目中,尽量用Qt的父子对象机制。它比智能指针更轻量,而且与Qt的事件循环完美配合。但如果你在写纯C++算法库,那就用 std::unique_ptr。

3.1.2 面向对象:多态是灵魂

面向对象三大特性——封装、继承、多态。封装和继承大家都会,但多态很多人用不好。说白了,多态就是「父类指针调用子类方法」。Qt里到处都是这个模式:你写一个 QWidget 的子类,然后把它当作 QWidget* 传给布局管理器。

class Base {
public:
    virtual void show() { std::cout << "Base\n"; }
};

class Derived : public Base {
public:
    void show() override { std::cout << "Derived\n"; }
};

void print(Base* b) {
    b->show(); // 多态:实际调用 Derived::show()
}

我曾经踩过的坑:忘记在析构函数前加 virtual。结果子类对象被父类指针删除时,只调了父类的析构函数,子类资源没释放。嗯,内存泄漏就这么来的。

3.1.3 模板与STL:写通用代码的利器

模板这东西,初看觉得麻烦,用多了就真香。Qt的容器和算法大量使用了模板。你想想看,QList<int> 和 QList<QString> 写法几乎一样,这就是模板的功劳。

template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// 使用
int m1 = max(3, 5);
double m2 = max(3.14, 2.71);

STL里的 std::vector、std::map 也是高频使用的。但注意,在Qt项目中,我更推荐用Qt自己的容器(后面会讲)。

3.2 Qt对C++的扩展:信号与槽、元对象系统

Qt最牛的地方,就是它用C++实现了一套「元对象系统」。说白了,就是让C++有了反射、动态属性、信号与槽这些能力。没有这套系统,Qt就是个普通的GUI库。

3.2.1 信号与槽:Qt的灵魂

信号与槽,本质上是观察者模式的一种实现。一个对象发出信号,另一个对象的槽函数被调用。关键是:信号和槽不需要知道对方的存在。这大大降低了耦合度。

// 定义
class MyButton : public QPushButton {
    Q_OBJECT
public:
    // ...
signals:
    void clicked(); // 信号:只声明,不实现
public slots:
    void onButtonClicked(); // 槽:普通成员函数
};

// 连接
connect(button, &QPushButton::clicked, this, &MyWidget::onButtonClicked);

为什么信号与槽这么重要?

  • 类型安全:编译期检查参数类型
  • 松耦合:发送者和接收者互不依赖
  • 线程安全:可以跨线程传递信号

我个人习惯,在写自定义组件时,一定会把「状态变化」设计成信号。比如一个温度计控件,温度变了就发 temperatureChanged(double) 信号。这样其他模块想监听温度变化,直接连这个信号就行,完全不用改温度计的代码。

3.2.2 元对象系统:Qt的「黑魔法」

元对象系统,说白了就是Qt在编译时给类加了一些「元信息」。比如类名、父类、属性列表、信号列表等。这些信息在运行时可以查询和操作。这就是反射。

要使用元对象系统,你的类必须满足三个条件:

  • 继承自 QObject
  • 在类声明开头写 Q_OBJECT 宏
  • 用 moc(元对象编译器)处理头文件
class MyClass : public QObject {
    Q_OBJECT
    Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
    int value() const;
    void setValue(int v);
signals:
    void valueChanged(int);
};

有了元对象系统,你可以做很多「常规C++做不到」的事:

  • 运行时获取类名:obj->metaObject()->className()
  • 动态调用方法:QMetaObject::invokeMethod()
  • 属性系统:obj->setProperty("value", 42)

我的经验:元对象系统虽然强大,但不要滥用。我见过有人把所有业务逻辑都塞进 QObject 子类里,结果编译慢得要死。记住:只有需要信号与槽、属性系统、运行时反射的类,才继承 QObject。

3.3 Qt容器与算法:比STL更贴心

Qt提供了一套自己的容器类,比如 QList、QMap、QHash 等。它们和STL容器功能类似,但有几个明显的优势:

  • 隐式共享(写时复制):拷贝容器时,只复制指针。只有真正修改内容时,才进行深拷贝。性能更好。
  • 与Qt信号与槽无缝集成:可以直接在信号中传递容器。
  • 更友好的API:比如 QList::append() 比 std::vector::push_back() 更直观。
  • 支持Java风格迭代器:foreach 宏(虽然C++11后不推荐了)。
// Qt容器
QList<int> list;
list << 1 << 2 << 3;  // 流式操作
list.append(4);

QMap<QString, int> map;
map["apple"] = 5;
map.insert("banana", 3);

// 遍历
for (auto key : map.keys()) {
    qDebug() << key << map[key];
}

Qt还提供了很多实用的算法函数,比如 qSort()、qFind()、qCopy() 等。不过C++11之后,STL的算法已经很强大了,我个人现在更倾向于用STL算法,因为更通用。

注意:Qt容器和STL容器可以互相转换,但要注意性能。比如 QList 转 std::vector 会触发深拷贝。我建议:在Qt项目内部,统一用Qt容器;在需要与第三方库交互时,再转成STL容器。

3.4 知识体系总览

下面这张图,帮你理清本章的核心脉络。你可以把它当作一个「知识地图」,随时回来查阅。

C++基础回顾与Qt扩展 · 知识体系 C++核心语法 • 内存管理 栈 vs 堆 智能指针 RAII原则 • 面向对象 封装 / 继承 多态(virtual) 虚析构 • 模板与STL 函数模板 / 类模板 vector / map 算法(sort/find) Qt对C++的扩展 • 信号与槽 观察者模式 类型安全 松耦合通信 • 元对象系统 Q_OBJECT宏 MOC编译器 运行时反射 • 属性系统 Q_PROPERTY 动态属性 setProperty/getProperty Qt容器与算法 • 常用容器 QList / QVector QMap / QHash QSet / QStringList • 核心特性 隐式共享(写时复制) Java风格迭代器 STL兼容接口 • 算法函数 qSort / qFind qCopy / qFill 与STL算法对比 三者结合:C++基础 + Qt扩展 + 容器算法 = 高效Qt开发

3.5 本章小结

这一章内容不少,但核心就三件事:

  • C++基础:内存管理、多态、模板——这些是写任何C++程序都绕不开的。别偷懒,一定要掌握。
  • Qt扩展:信号与槽、元对象系统——这是Qt的「独门绝技」,也是你从「会用Qt」到「懂Qt」的分水岭。
  • Qt容器与算法:比STL更贴心,但也要注意使用场景。我的建议是:Qt项目内用Qt容器,跨项目用STL。

嗯,基础就回顾到这里。下一章咱们开始动手写真正的Qt项目。到时候你会发现,今天讲的这些东西,全都会用上。


可可精品资料库