Qt 容器和类型
容器
QString
QString 是 Qt 的 Unicode 字符串类型,以 UTF-16 保存文字,并提供查找、截取、替换和编码转换等操作。
QString 与 std::string 的不同
存储编码、操作单位和配套功能。
| 对比 | std::string | QString |
|---|---|---|
| 编码 | 本身不规定编码,常约定为 UTF-8 | UTF-16 |
| size() | char 数量,存 UTF-8 时就是字节数 | UTF-16 码元数 |
| 截取单位 | char 字节 | UTF-16 码元 |
| 文本工具 | 基本拼接、查找、截取等 | 另有 Unicode 大小写、字符分类、规范化和编码转换等 |
| 普通复制 | 建立独立字符存储 | 通常先共享存储,修改时分离 |
| 接口配合 | 标准 C++ 及许多第三方库 | Qt 控件、QML、模型、网络等 |
Unicode 给字符规定编码标识;UTF-8、UTF-16 则规定这些字符怎样存成字节或码元。
1 | // 假设源文件和窄字符串字面量使用 UTF-8 编码。 |
QStringLiteral
QStringLiteral("文字") 是 Qt 提供的宏,用来从编译期已知的字符串字面量构造 QString。它不是新的字符串类型,得到的结果仍然是 QString。
1 | // 普通窄字符串转为 QString:Qt 按 UTF-8 解释并准备 UTF-16 数据。 |
二者最终表达同一段文字,但构造路径不同:
1 | 普通窄字面量 → 运行期转换为 UTF-16,并准备字符缓冲 → QString |
QStringLiteral 可以避免这一步字面量构造中的运行期编码转换和字符堆缓冲分配。
误区
它只能包装字面量,不能转换运行时变量。
1 | // 网络返回的文字在运行时才知道,明确按 UTF-8 转换。 |
在项目中的用法
房间号、主播名、直播标题、弹幕、日志等都使用 QString。例如 RoomService.cpp 用它处理房间信息和请求提示。
1 | // 用户输入可能带空格,先清理首尾空白。 |
常用操作:
1 | // 查找文字,不修改原字符串。 |
项目里的典型用途是固定日志模板、固定字段名和资源路径:
1 | // 源码使用的形式:固定 shader 资源路径在编译期就已确定。 |
QByteArray
QByteArray 保存原始字节,不要求内容是文字,可以包含二进制包头、压缩数据和零字节。
1 | // 显式指定长度,三个字节分别是 A、零字节、B。 |
文字与字节互转:
1 | // 将 Unicode 文字编码成 UTF-8 字节。 |
与 STL 相比,它的用途接近 std::vector<char>、std::vector<std::byte> 或用于二进制存储的 std::string,并方便接入 Qt 文件、网络和 Base64 接口。
QByteArray 内容中允许 \0,不要用 strlen() 判断这种二进制数据的长度,应使用 size()。
交给 C 接口时还要注意拥有者寿命:
1 | // bytes 保持存活且不进行导致指针失效的修改时,pointer 可用于读取。 |
在项目中的用法
DanmakuPacket.cpp 用它接收和拆解弹幕协议包;VideoMaterial.cpp 也通过字节数组写 shader uniform。
QList / QVector
Qt 6 中 QVector 是 QList 的别名,QList 使用连续存储,概念上接近 std::vector;它不是 std::list 双向链表。
两者都适合按下标访问和顺序遍历。中间插入、删除通常需要移动后续元素。Qt 容器支持隐式共享,std::vector 的普通复制则建立独立元素存储,详见隐式共享与写时复制。
容量不等于元素数量:
1 | // reserve() 仅预留空间,还没有实际元素。 |
在项目中的用法
1 | // 保存逻辑位置对应的物理身份;下标从 0 开始。 |
项目使用它保存布局矩形、播放列表条目和热点时间桶:
1 | // 源码中的类型别名:一组布局矩形。 |
对应 LayoutTree.hpp、PlaylistEntries.hpp 和 LiveHotspotDetector.hpp
QStringList
QStringList 基于 QList<QString> 是字符串列表,增加了一组字符串常用的处理方法。std::vector<std::string> 可以保存同类数据,但没有作为该容器成员的 join()。QStringList 集中了文本列表的便利操作。
在项目中的用法
项目的画质名称、线路名称、Cookie 拆分都使用它。
1 | // 拆分得到“原画”“高清”“流畅”三个元素。 |
项目 Cookie 处理的形式:
1 | // 一段 Cookie 拆成若干键值项,跳过空项。 |
QMap
QMap 接近 std::map:按键排序,查找通常为 O(log n);额外提供 Qt 接口及隐式共享。
在项目中的用法
项目用 QMap<QString, QString> 将 Cookie 拆成名称和值。
1 | // Cookie 名称对应 Cookie 值。 |
遍历时分别读取键和值:
1 | // 只读遍历,按键顺序访问。 |
源码见 CredentialStore.cpp 的 parseCookieMap()、buildCookieString()。
读取时注意:
1 | // value() 找不到键时返回默认值,不会插入新项。 |
QHash
QHash 接近 std::unordered_map:查找平均 O(1),不保证按键排序。哈希冲突、扩容等仍会影响成本。
需要按键排序通常选 QMap;主要按键查值而不关心顺序可以选 QHash。
在项目中的用法
项目的典型用途是模型角色编号与 QML 字段名之间的映射。
1 | // 教学示意:编号是键,不要求连续,也不等于列表下标。 |
实际入口是 PlaylistEntries.cpp 的 roleNames()。
QSet
QSet 接近 std::unordered_set。
在项目中的用法
项目用它记录已弹出的格子、已直接关闭的格子,以及检查身份映射是否重复。
1 | // 记录已弹出的逻辑格子。 |
LayoutEditor.hpp 的实际成员为 QSet<int> m_poppedOut, m_discarded。
QQueue
QQueue 基于 QList。QQueue 本身不是线程安全的阻塞队列,也不会自动限制积压。
在项目中的用法
项目用它保存播放列表刷新请求。
1 | // 两个房间依次等待刷新。 |
Playlist.cpp 使用 enqueueRoom() 入队,再由调度逻辑取出 PlaylistRefreshRequest 发起请求。
项目媒体包队列使用 PacketQueue.hpp 中的 std::deque、mutex 和 condition_variable,另外实现容量、背压、等待和唤醒。
QVariantMap / QVariantList
两者的类型关系如下
1 | // 类型关系示意:字符串键对应一个 QVariant 值。 |
可以把 QVariantMap 理解为“一条带字段的数据”,QVariantList 理解为“一批数据”。它们便于通用 Qt 数据交换,但字符串键的拼写错误通常不会被编译器发现。
例如把 "title" 写成 "titel" 仍可能编译通过。因此项目内部字段明确的数据继续使用 PlaylistEntry、TileAssignment 等结构体,而不是把所有业务对象改为 QVariantMap。
与 STL 组合相比,可以用 std::map<std::string, std::variant<...>> 和 std::vector<std::variant<...>> 表达类似结构;Qt 这组别名的优势是可以直接配合 QVariant、属性与 QML 接口。
在项目中的用法
布局预览可以这样表达:
1 | // 一个格子的字段中同时包含文字和浮点数。 |
项目的 LayoutEditor.cpp 用这种结构提供布局卡片与预览;热点模块也使用 QVariantMap 传递元信息。
隐式共享、写时复制与 STL 的区别
先共享,写入时再分离
QString、QByteArray、QList、QMap 等容器采用隐式共享,只传递指向数据的指针,并且只有当函数写入数据时才会复制数据,即写时复制。共享类由一个指向共享数据块的指针组成,该数据块包含引用计数和数据。
创建共享对象时,它会将引用计数设置为 1。每当有新对象引用共享数据时,引用计数就会递增;当对象取消引用共享数据时,引用计数就会递减。当引用计数变为零时,共享数据将被删除。在处理共享对象时,有两种复制对象的方式。我们通常称之为深拷贝和浅拷贝。深拷贝意味着复制一个对象。浅拷贝是引用拷贝,即仅指向共享数据块的指针。进行深拷贝在内存和 CPU 方面可能代价高昂。进行浅拷贝非常快,因为它只涉及设置指针和增加引用计数。
1 | // 原始布局身份列表。 |
1 | 复制后: |
std::vector 的普通复制通常立即建立独立元素存储。Qt 将可能的大复制延后到需要写入的时候,因此第一次修改共享副本可能比预想的昂贵。
非 const 访问也可能触发分离
容器不知道你取得可写引用后是否真的会改,所以非 const 的 begin()、operator[] 等调用也可能触发分离。
1 | // 只读遍历:把容器转为 const 访问,选择只读迭代器。 |
仅把循环变量写成 const auto&,不保证容器使用 const begin();std::as_const() 才是将访问对象变为 const。它声明在标准头文件 <utility> 中。
分离、扩容和修改可能使已有迭代器、引用或裸指针失效。不要拿着一个元素引用,再随意复制或修改相关容器。
复制容器,不等于复制指针指向的对象
1 | // 假设 object 是一个仍然有效的 QObject;两个容器保存同一对象的地址。 |
这时通过 a 中的指针也能观察到名字变化。STL 容器复制裸指针或 shared_ptr 元素也存在类似的“指向同一个对象”语义。
QObject 本身不能像 QString 一样进行值复制。不要把隐式共享与 QObject 父子所有权、shared_ptr 共享所有权混为一谈。
隐式共享不是自动线程安全
不同线程操作各自的值副本,与多个线程同时读写同一个容器对象,是不同情况。引用计数机制不能代替后者所需的同步。
在项目中的用法
项目布局算法经常复制一组矩形,再计算调整后的结果。只读时共享可以减少复制;真正修改后,两份布局仍互不影响。
项目媒体层仍需要 mutex、condition_variable 和明确的只读帧契约,不能因为外面用了 Qt 类型就省掉线程协调。
Qt 容器与 STL 在项目中分别承担以下任务:
| 任务 | 项目例子 | 原因 |
|---|---|---|
| 文本和 Qt 接口 | QString、QStringList | 与网络、QML、日志接口配合直接 |
| 列表业务与展示 | QVector |
保存结构化条目,并通知视图 |
| 通用字段交换 | QVariantMap、QJsonObject | 分别负责 Qt 值传递与 JSON 序列化 |
| 原始 Y/UV 像素 | std::vector |
连续字节缓冲,方便媒体数据访问 |
| 多线程包队列 | std::deque<AVPacket*> 加锁和条件变量 | 明确队列、包寿命与等待策略 |
| BSP 子节点 | std::unique_ptr |
表达唯一所有权 |
| 跨线程帧持有 | std::shared_ptr |
延长帧寿命并限制消费者写入 |
Qt 容器也能使用许多 STL 算法:例如布局代码直接对 QList 的迭代器调用 std::sort。
类型
QVariant
QVariant 是携带类型信息的通用值,可以保存整数、字符串、布尔值,以及符合 Qt 元类型要求的其他类型。
1 | // 同一个 QVariant 可以先保存整数,再保存字符串。 |
转换时要检查结果:
1 | // 字符串不能解析为整数,不能把返回的零误认为正常业务值。 |
QVariant 不是所有类型间都能自动正确转换。
STL 中的 std::variant 在编译期明确列出候选类型:
1 | // 只能保存这些候选类型中的一种,不是任意类型盒子。 |
QVariant 更方便接 Qt 属性、模型和 QML;std::variant 适合候选类型明确、希望编译期检查的业务数据。也不要把 QVariant 与无类型裸内存混淆,它保留了类型信息。
两者底层都使用了模板,区别在于模板参数有没有成为对象类型的一部分:
1 | // 两个不同的类模板实例,是两种不同的 C++ 类型。 |
QVariant::fromValue<T>() 内部利用模板获知 T,取得对应的类型信息,并把对象放入存储。之后统一用 QVariant 表示,通过保存的类型信息处理它。这个过程叫类型擦除:对外隐藏具体类型,但内部仍保留管理它所需的信息。
std::variant |
QVariant |
|
|---|---|---|
| 底层使用模板 | 是 | 是 |
| 类本身是类模板 | 是 | 否 |
| 具体类型如何表达 | 候选类型属于模板参数 | 当前类型记录在对象内部的类型信息中 |
| 对外是什么类型 | variant<int, QString> 等不同类型 |
统一为 QVariant |
在项目中的用法
项目模型的 data() 只有一种返回类型 QVariant,但不同角色要返回不同数据:
1 | // 模型内部教学片段:返回值被统一包装成 QVariant。 |
QJsonObject / QJsonArray / QJsonDocument
| 类型 | 负责什么 |
|---|---|
QJsonObject |
表示 JSON 的花括号对象 {} |
QJsonArray |
表示 JSON 的方括号数组 [] |
QJsonDocument |
将对象或数组解析自 JSON 字节,或序列化为 JSON 字节 |
QVariantMap 与 QJsonObject 不同:前者可以保存许多 Qt 或注册的 C++ 类型,后者受 JSON 的字符串、数字、布尔、数组、对象、null 等格式约束。QVariant 是程序内部的通用值,JSON 是序列化格式。
标准库本身没有对应的完整 JSON 解析器,使用 STL 的项目通常另外引入 JSON 库。
在项目中的用法
1 | // 创建一个带多个字段的 JSON 对象。 |
反过来解析:
1 | // 先检查 JSON 语法,再确认顶层类型是否为数组。 |
项目用它们解析平台接口响应,并保存布局树和身份映射。上面省略了业务字段逐项校验。
QAbstractListModel
它是数据与视图之间的协议。三个基本接口分别回答:
rowCount():一共有多少行?data():某行的某个字段是什么?roleNames():QML 使用什么名称访问字段?
STL 容器只管理数据,不提供这种视图通知;QAbstractListModel 负责把数据变化解释给 Qt 的视图系统。
在项目中的用法
项目播放列表有两层:
1 | QVector<PlaylistEntry>:保存房间数据 |
1 | // 模型内部简化片段:把角色编号映射为 QML 字段名。 |
项目添加房间时,先声明插入、再改数据、最后结束插入:
1 | // 模型内部教学片段:通知视图第 0 行即将插入。 |
对应 PlaylistEntries.cpp。只执行 m_rooms[0].title = ... 会改变内存,但 QML 不会自动获知普通 C++ 容器元素的变化。
QRectF / QSize
QRectF 表示浮点矩形,包含位置 x、y 和尺寸 width、height。QSize 只表示整数宽高,不含位置;QSizeF 表示浮点宽高。
标准库没有 GUI 几何类型;std::pair 虽然能放两个数,但没有宽高语义和几何方法。
在项目中的用法
项目布局通常先使用 0~1 的归一化坐标,显示时再映射为像素。
1 | // 位于左半边、铺满高度的归一化矩形。 |
包围盒用于合并或联动计算:
1 | // 两块区域的最小外接矩形。 |
united() 得到包围盒,并不保证输入区域无空洞,所以项目还需要面积等合法性检查。
1 | // 以像素表示视频或纹理的尺寸。 |
源码见 LayoutTree.cpp、VideoGeometry.cpp
QUrl / QUrlQuery
结构化处理地址与参数
URL 包含协议、主机、路径、查询参数。
QUrlQuery 便于按查询参数结构维护和编码,减少手工处理问号、连接符和特殊字符的错误。
在项目中的用法
1 | // 地址主体与查询参数分开组织。 |
项目入口是 RoomService.cpp 的 requestStream()。
QDateTime / QElapsedTimer
QDateTime 回答“什么时候”
用途大致对应 std::chrono::system_clock 的时间点,但 QDateTime 还提供日期、格式化、时区等 Qt 接口。涉及跨时区或 UTC 数据时,要确保双方时间含义一致。
QElapsedTimer 回答“经过多久”
1 | // 启动间隔测量,不表示某年某月某日。 |
Qt 6.8 的 QElapsedTimer 使用单调时钟,接近 std::chrono::steady_clock,不会因手动调整系统日历时间而跳变
QElapsedTimer 不会自己周期触发回调,周期事件是 QTimer 的职责。这里 QTimer 决定何时统计,QElapsedTimer 测量真实经过的时长。
在项目中的用法
项目用 QDateTime 记录开播时间、最近刷新时间等。
1 | // 记录这次刷新发生的日历时间。 |
项目 FPS 统计的核心片段:
1 | // 源码摘录:获取实际间隔,同时将起点重置为现在。 |
见 VideoSurface.cpp
qint64 / quint64 / qsizetype
| Qt 类型 | 标准类型的近似对应 | 关键点 |
|---|---|---|
qint64 |
std::int64_t |
有符号,64 位 |
quint64 |
std::uint64_t |
无符号,64 位 |
qsizetype |
用途接近 std::ptrdiff_t |
有符号尺寸类型,位宽随平台 |
qsizetype 与 size_t 不完全一样,size_t 是无符号的。混合有符号和无符号值进行比较、减法时,要留意转换结果。
在项目中的用法
1 | // 有符号 64 位整数,保存毫秒时间戳。 |
项目弹幕解析使用 qsizetype offset,与 QByteArray::size() 配合,避免把较宽的长度放进 int 发生截断。
1 | // 长度和偏移采用相同类型,计算剩余数据量。 |
QPointer
观察 QObject,对象销毁后自动清空QPointer 不拥有对象,只观察 QObject 派生对象是否仍然存在。
1 | // 独立教学示例:对象由其他代码负责销毁。 |
普通裸指针在 delete 后通常仍保留旧地址,成为悬空指针;QPointer 会在 QObject 销毁时被清空。
| 类型 | 是否拥有对象 | 失效/释放方式 |
|---|---|---|
| T* | 类型本身不表达所有权 | 对象销毁后可能悬空 |
| QPointer |
不拥有,只适用于 QObject 派生类 | QObject 销毁后自动清空 |
| std::unique_ptr |
独占所有权 | 拥有者释放对象 |
| std::shared_ptr |
共享所有权 | 最后一个拥有者释放对象 |
| std::weak_ptr |
不拥有,观察 shared_ptr 管理的对象 | 用 lock() 尝试取得临时共享所有权 |
QPointer 不是跨线程寿命锁,不能认为“检查非空后,对象就绝不会在另一线程销毁”。它也不同于 shared_ptr,不提供临时保活的 lock()。
在项目中的用法
项目 VideoSurface.hpp 中的 QPointer<PlaybackSession> m_session 表示显示组件观察会话,不靠这个指针延长会话寿命。
1 | // 源码中的读取形式:会话不存在时不再调用取帧方法。 |

