01-Qt基础-C++基础回顾

C++ 基础回顾(Qt 实战版)

Qt 是 C++ 框架,写 Qt 之前必须把 C++ 核心概念过一遍。本章只讲 Qt 开发中天天用到的部分,每个知识点都配 Qt 场景示例。


一、类与对象

1.1 基本语法

#include <QString>

/**
 * @brief 用户类,演示封装、构造函数、成员函数
 */
class User
{
public:
    /**
     * @brief 构造函数
     * @param name 用户名
     * @param age 年龄
     */
    User(const QString &name, int age)
        : m_name(name), m_age(age)  // 初始化列表(比赋值更高效)
    {
    }

    /**
     * @brief 获取用户名(const 成员函数,不修改对象状态)
     * @return 用户名
     */
    QString name() const { return m_name; }

    /**
     * @brief 设置用户名
     * @param name 新用户名
     */
    void setName(const QString &name) { m_name = name; }

    /**
     * @brief 获取年龄
     * @return 年龄
     */
    int age() const { return m_age; }

private:
    QString m_name;  ///< 成员变量加 m_ 前缀是 Qt 惯例
    int     m_age;
};

1.2 Qt 中的类

Qt 里几乎所有东西都是类,控件是类、窗口是类、甚至字符串 QString 也是类。写 Qt 就是继承 Qt 的类,重写虚函数,连接信号槽


二、继承与多态

2.1 继承

#include <QWidget>
#include <QPainter>

/**
 * @brief 自定义圆形控件,继承 QWidget
 *
 * Qt 中自定义控件的标准做法:继承 QWidget,重写 paintEvent。
 */
class CircleWidget : public QWidget
{
    Q_OBJECT  // 必须加这个宏才能用信号槽、tr() 等特性

public:
    /**
     * @brief 构造函数
     * @param parent 父对象,传入后内存由父对象管理
     */
    explicit CircleWidget(QWidget *parent = nullptr)
        : QWidget(parent)
    {
        setMinimumSize(100, 100);
    }

protected:
    /**
     * @brief 重写绘制事件(虚函数多态)
     * @param event 绘制事件对象
     *
     * 系统需要重绘控件时自动调用,我们只负责画什么。
     */
    void paintEvent(QPaintEvent *event) override
    {
        Q_UNUSED(event);  // 标记参数未使用,避免编译器警告
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.setBrush(Qt::blue);
        painter.drawEllipse(rect().adjusted(5, 5, -5, -5));
    }
};

2.2 多态关键点

  • override 关键字:显式标记重写虚函数,编译器会检查签名是否匹配。
  • virtual 析构函数:基类析构函数必须是虚函数,否则通过基类指针删除派生类对象会内存泄漏。Qt 的 QObject 析构函数已经是虚的。
  • 纯虚函数:virtual void func() = 0;,包含纯虚函数的类是抽象类,不能实例化。

三、指针与引用

3.1 指针

Qt 中控件几乎都用指针操作,因为:

  1. 控件对象通常在堆上创建,生命周期由对象树管理。
  2. 多态必须通过指针或引用实现。
#include <QPushButton>

/**
 * @brief 演示 Qt 控件指针的创建与父子关系
 */
void demoPointer()
{
    // 在堆上创建按钮,父对象是 window
    // window 销毁时会自动 delete 这个按钮,不需要手动释放
    QPushButton *btn = new QPushButton("确定", &window);

    // 用 -> 访问成员
    btn->setText("OK");
    btn->show();

    // 空指针检查
    if (btn != nullptr) {
        btn->setEnabled(true);
    }
}

3.2 引用

引用常用于函数参数,避免拷贝,比指针更安全(不能为空)。

#include <QString>
#include <QStringList>

/**
 * @brief 拼接字符串列表
 * @param list 字符串列表(const 引用,避免拷贝且不修改)
 * @param sep 分隔符
 * @return 拼接后的字符串
 */
QString joinList(const QStringList &list, const QString &sep)
{
    QString result;
    for (const QString &s : list) {  // 范围 for + 引用,高效遍历
        if (!result.isEmpty()) result += sep;
        result += s;
    }
    return result;
}

Qt 惯例QStringQByteArray 等容器类传参用 const &,控件类传指针。


四、STL 容器

Qt 有自己的容器类(QListQMapQStringList 等),也兼容 STL。两者都要会。

4.1 常用容器对比

用途STLQt 推荐
动态数组std::vector<T>QList<T>
字符串列表std::vector<std::string>QStringList
键值对std::map<K,V>QMap<K,V>
哈希表std::unordered_map<K,V>QHash<K,V>
字符串std::stringQString

4.2 QList 实战

#include <QList>
#include <QDebug>

/**
 * @brief 演示 QList 常用操作
 */
void demoQList()
{
    QList<int> numbers;

    // 增
    numbers << 10 << 20 << 30;        // 流式插入,Qt 特色
    numbers.append(40);
    numbers.prepend(5);               // 头部插入
    numbers.insert(2, 15);            // 在索引 2 处插入

    // 查
    qDebug() << numbers[0];           // 10
    qDebug() << numbers.first();      // 第一个
    qDebug() << numbers.last();       // 最后一个
    qDebug() << numbers.size();       // 大小
    qDebug() << numbers.contains(20); // 是否包含

    // 删
    numbers.removeAt(0);              // 删除索引 0
    numbers.removeAll(20);            // 删除所有值为 20 的元素
    numbers.clear();                  // 清空

    // 遍历
    QList<QString> fruits = {"apple", "banana", "cherry"};
    for (const QString &f : fruits) {
        qDebug() << f;
    }

    // 带索引遍历
    for (int i = 0; i < fruits.size(); ++i) {
        qDebug() << i << fruits[i];
    }
}

4.3 QMap / QHash 实战

#include <QMap>
#include <QHash>
#include <QDebug>

/**
 * @brief 演示 QMap 与 QHash
 */
void demoMap()
{
    // QMap:有序(按 key 排序),基于红黑树
    QMap<QString, int> scores;
    scores["Alice"] = 95;
    scores["Bob"] = 88;
    scores["Charlie"] = 92;

    // 遍历(按 key 字母序输出)
    for (auto it = scores.begin(); it != scores.end(); ++it) {
        qDebug() << it.key() << it.value();
    }

    // QHash:无序,基于哈希表,查找更快
    QHash<QString, QString> config;
    config["host"] = "127.0.0.1";
    config["port"] = "8080";
    qDebug() << config.value("host", "localhost");  // 带默认值
}

五、智能指针

C++ 裸指针容易内存泄漏,智能指针自动管理生命周期。但 Qt 有对象树机制,控件通常不需要智能指针。智能指针主要用于非 QObject 的业务对象

5.1 unique_ptr

#include <memory>
#include <QDebug>

/**
 * @brief 业务数据类(非 QObject)
 */
class Config
{
public:
    Config() { qDebug() << "Config 构造"; }
    ~Config() { qDebug() << "Config 析构"; }
    void load() { qDebug() << "加载配置"; }
};

/**
 * @brief 演示 unique_ptr:独占所有权,离开作用域自动释放
 */
void demoUniquePtr()
{
    auto cfg = std::make_unique<Config>();  // C++14 推荐写法
    cfg->load();

    // 函数结束时 cfg 自动 delete,不会泄漏
}

5.2 shared_ptr

#include <memory>

/**
 * @brief 演示 shared_ptr:共享所有权,引用计数为 0 时释放
 */
void demoSharedPtr()
{
    auto p1 = std::make_shared<Config>();
    {
        auto p2 = p1;  // 引用计数 +1
        qDebug() << "引用计数:" << p1.use_count();  // 2
    }  // p2 销毁,引用计数 -1

    qDebug() << "引用计数:" << p1.use_count();  // 1
}  // p1 销毁,引用计数 0,对象释放

5.3 Qt 中的智能指针

Qt 也提供了自己的智能指针:

  • QScopedPointer<T>:类似 unique_ptr,独占。
  • QSharedPointer<T>:类似 shared_ptr,共享。
  • QPointer<T>专门用于 QObject,对象被销毁后自动置空(弱引用),非常实用。
#include <QPointer>
#include <QPushButton>

/**
 * @brief 演示 QPointer:监控 QObject 生命周期
 */
void demoQPointer()
{
    QPointer<QPushButton> btn = new QPushButton("测试");
    qDebug() << "是否有效:" << !btn.isNull();  // true

    delete btn;  // 模拟对象被销毁(比如父窗口释放了它)

    qDebug() << "是否有效:" << !btn.isNull();  // false,自动置空,不会野指针
    if (btn) {  // 安全检查
        btn->setText("安全");
    }
}

QPointer 是 Qt 独有的利器:当你持有一个控件指针,但不确定它是否还活着时,用 QPointer 包一下,判空即可,绝不会野指针。


六、Lambda 表达式

Lambda 是 C++11 引入的匿名函数,在 Qt 中写槽函数时极其常用,省去单独写成员函数的麻烦。

6.1 语法

[捕获列表](参数列表) mutable -> 返回类型 {
    函数体
}

6.2 Qt 中的典型用法

#include <QPushButton>
#include <QLabel>
#include <QVBoxLayout>

/**
 * @brief 演示 Lambda 作为槽函数
 * @param parent 父窗口
 */
void setupLambdaDemo(QWidget *parent)
{
    auto *layout = new QVBoxLayout(parent);
    auto *label  = new QLabel("0", parent);
    auto *btn    = new QPushButton("加1", parent);
    layout->addWidget(label);
    layout->addWidget(btn);

    int count = 0;

    // 连接信号到 Lambda
    // [=]:值捕获所有外部变量(count、label 都会被拷贝一份)
    // mutable:允许修改值捕获的 count
    QObject::connect(btn, &QPushButton::clicked, [=]() mutable {
        ++count;
        label->setText(QString::number(count));
    });

    // [&]:引用捕获(注意生命周期,控件被销毁后引用会悬空)
    // [label]:只捕获 label
    // [this]:捕获当前对象指针(在类成员函数中常用)
}

6.3 捕获方式总结

写法含义使用场景
[]不捕获纯逻辑,不依赖外部变量
[=]值捕获所有用到的变量控件指针、简单值(最常用)
[&]引用捕获所有用到的变量需要修改外部变量,且确保生命周期
[this]捕获当前对象指针类成员函数中访问成员变量
[=, &x]默认值捕获,x 用引用捕获混合场景

Qt 中 Lambda 连接信号的注意事项:如果 Lambda 中访问了某个控件,确保该控件在信号触发时仍然存在。用 [=] 捕获控件指针是安全的,因为指针是值拷贝。


七、核心要点总结

  1. 类与对象:Qt 一切皆对象,自定义控件继承 QWidget,重写虚函数。
  2. 继承多态override 关键字必加,Q_OBJECT 宏是信号槽的前提。
  3. 指针引用:Qt 控件用指针(堆上 + 对象树管理),数据类型用 const & 传参。
  4. 容器:优先用 Qt 容器(QListQMapQString),与 Qt API 无缝衔接。
  5. 智能指针:非 QObject 业务对象用 unique_ptr/shared_ptr;QObject 用 QPointer 防野指针。
  6. Lambda:写短槽函数的首选,[=] 值捕获最常用,注意控件生命周期。
  7. 初始化列表:构造函数中用初始化列表比函数体内赋值更高效,是 Qt 惯例。

下一章:Qt 基础 - 对象树与内存管理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值