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 中控件几乎都用指针操作,因为:
- 控件对象通常在堆上创建,生命周期由对象树管理。
- 多态必须通过指针或引用实现。
#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 惯例:
QString、QByteArray等容器类传参用const &,控件类传指针。
四、STL 容器
Qt 有自己的容器类(QList、QMap、QStringList 等),也兼容 STL。两者都要会。
4.1 常用容器对比
| 用途 | STL | Qt 推荐 |
|---|---|---|
| 动态数组 | 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::string | QString |
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 中访问了某个控件,确保该控件在信号触发时仍然存在。用
[=]捕获控件指针是安全的,因为指针是值拷贝。
七、核心要点总结
- 类与对象:Qt 一切皆对象,自定义控件继承 QWidget,重写虚函数。
- 继承多态:
override关键字必加,Q_OBJECT宏是信号槽的前提。 - 指针引用:Qt 控件用指针(堆上 + 对象树管理),数据类型用
const &传参。 - 容器:优先用 Qt 容器(
QList、QMap、QString),与 Qt API 无缝衔接。 - 智能指针:非 QObject 业务对象用
unique_ptr/shared_ptr;QObject 用QPointer防野指针。 - Lambda:写短槽函数的首选,
[=]值捕获最常用,注意控件生命周期。 - 初始化列表:构造函数中用初始化列表比函数体内赋值更高效,是 Qt 惯例。
下一章:Qt 基础 - 对象树与内存管理
317

被折叠的 条评论
为什么被折叠?



