Qt 10 字符与字符串基础(C++ char / std::string 配合 Qt 上下文)学习笔记
本文介绍Qt中QString与QMap的字符串处理与文本解析,并说明按字符索引、拷贝语义及Qt6返回值类型等要点与边界。
Qt 10 字符与字符串基础(C++ char / std::string 配合 Qt 上下文)学习笔记
本章重要 API 函数
| API | 功能 | 参数说明 | 头文件 |
|---|---|---|---|
QString::append/prepend | 尾部/头部追加字符串 | 接受 QString 或 const char*(当 UTF-8) | <QString> |
QString::insert(int pos, const QString &) | 在指定位置插入 | pos:插入索引(基于 UTF-16 字符数);源码 test01 用 str1.insert(1, "们") | <QString> |
QString::mid(int pos, int n = -1) | 取子串 | pos 起始索引;n 长度,默认到末尾。源码 test02 用 mid(last_pos+1) 取文件名 | <QString> |
QString::left(int n) | 取左边 n 个字符 | n:字符数(非字节) | <QString> |
QString::lastIndexOf/indexOf | 反向/正向查找 | 返回索引 qsizetype;找不到返回 -1。源码用于切分路径与 K-V | <QString> |
QMap<Key, T> | Qt 关联容器(红黑树) | 类似 std::map,按键有序;源码 test04 演示 insert 与 [] 赋值 | <QMap> |
💡 Tips:Qt6 起字符串索引/长度返回
qsizetype(64 位有符号),不再是int——在 32 位循环里用int接受返回值会触发截断告警。另外QString::mid的pos是”字符数”不是”字节数”,对中文同样按一个QChar计,这正是 UTF-16 定长索引的优势(见 qt09)。
本章目录
点击条目可跳转到对应小节。
章节导航
- 本章重要 API 函数
- 学习前后依赖
- 1. 概述
- 2. 核心概念 / 原理
- 3. C++ 字符串模型与 Qt 衔接
- 4. 核心代码解析
- 5. 运行效果 / 验证
- 容易踩坑(注意点)
- 学习要点总结
- Q&A(常见问题)
- Tips(全局速记)
学习前后依赖
| 方向 | 内容 |
|---|---|
| 前置基础 | C/C++ 基本语法、char 单字符、char* 字符数组、std::string 常用接口。 |
| 本章核心 | 区分字符/字节/编码;掌握 QString 增删改查与子串切分;理解 Qt 容器 QMap。 |
| 后续承接 | qt09 的编码转换依赖本章的 char/编码概念;qt11 布局里的中文文案与字符串处理、qt22 SQLite 文本字段都建立在此之上。 |
💡 Tips:章节顺序上 qt10 本应在 qt09 之前(先懂 char/string 再学转换),但仓库编号把 qt09 放前面也不冲突——两者互为镜像:qt09 讲”QString 与外部字节如何转”,qt10 讲”字符串本身怎么玩”。建议把本篇当作 qt09 的”底层铺垫篇”来回对照读。
1. 概述
字符串是 GUI 程序里出现频率最高的数据类型:按钮文字、文件路径、网络报文、配置键值……C++ 提供了三层字符串抽象:最底层是 char(单个字节,常用来表示 ASCII 字符)、char*(指向字符数组的指针,C 风格字符串以 \0 结尾)、std::string(C++ 标准动态字符串,自动管理内存)。它们都只是字节序列,不携带编码信息。
Qt 在 std::string 之上提供了 QString(见 qt09),并配一整套容器(顺序容器 QList、关联容器 QMap/QHash 等)来组织数据。qt10 的 hello.cpp 虽以 QString 操作为主体,却恰好覆盖了”字符串基础”的全部核心动作:拼接(append/prepend/+=)、插入(insert)、子串提取(mid/left)、查找定位(indexOf/lastIndexOf)、以及用 QMap 存结构化数据。它本质上是”在 Qt 上下文里练字符串基本功”。
💡 Tips:概述里最该记住的一句话——“char* 是字节、std::string 是字节容器、QString 是 Unicode;操作字符串前先想清楚你手里的是哪一层。” 中文乱码、索引错位,几乎都源于把这三层混为一谈。
2. 核心概念 / 原理
2.1 字符、字节与编码
- 一个
char占 1 字节(8 位),只能直接表示 0–255。ASCII 用 0–127 表示英文字符;中文需用多字节(UTF-8 下”你”占 3 字节,GBK 下占 2 字节)。 char*字符串以\0结尾,strlen数的是字节数——所以strlen("你好")在 UTF-8 下返回 6 而非 2。这是初学者最容易踩的”索引/长度按字节还是按字符”的坑。QString用QChar(UTF-16)数组,length()返回字符数,"你好".length()是 2。这就是 qt09 强调的”UTF-16 定长索引”优势在字符串处理上的直接体现。
2.2 QString 的不可变片段与容器
QString 的 append/prepend/insert 都会产生新内容(隐式共享下可能触发 detach,见 qt09)。而 mid/left 返回的是新 QString 子串(拷贝),不是原串的视图——这与 C++17 std::string_view/QStringView 不同,需要注意大字符串频繁切片的开销。
QMap 是 Qt 的有序关联容器(底层红黑树),按键排序,支持 [] 下标与 insert。它对应标准库的 std::map。Qt 还提供 QHash(哈希,无序、查找 O(1))对应 std::unordered_map。
💡 Tips:原理层最易误解的是”
mid返回的是原串的一部分(视图)“。错,它返回独立拷贝的子串;若要零拷贝视图用 Qt6 的QStringView。另一个误区:“indexOf找的是字节位置”——在 QString 里它找的是QChar位置(字符位置),与std::string::find的字节语义一致但单位不同,对纯 ASCII 无差、对中文要留心。
3. C++ 字符串模型与 Qt 衔接
下面用 classDiagram 展示从 C++ 原生字符串到 Qt 字符串/容器的层次关系:
层次含义:char 是原子;char*/std::string 是字节层;QString 在字节层之上引入 Unicode 与丰富 API;QMap 等容器用来组织多个字符串/数据。
💡 Tips:图中最关键的边是
std_string <|.. QString——两者通过toStdString/fromStdString互转,但转换时默认按 UTF-8(见 qt09)。记住这条桥,就能在”标准库代码”与”Qt 代码”之间无缝搬运字符串,而不丢失编码。
4. 核心代码解析
qt10_tst-char/hello.cpp 四个测试,逐段拆解(去掉了注释掉的 #if 0 部分,保留实际运行逻辑):
void test01() { QString str1("hello"); qDebug() << str1; str1.append('_').prepend("你好,"); // 尾加 '_',头加 "你好," str1 += "!!"; // += 等价于 append qDebug() << str1; // "你好,hello_!!" str1.insert(1, "们"); // 在索引1处插入 qDebug() << str1; // "你们好,hello_!!"}append/prepend/+=都返回QString&,可链式;insert(1,"们")把”们”插到索引 1(“好”之前),体现 UTF-16 按字符定位。str1是QString(UTF-16),构造自char*字面量默认 UTF-8(qt09 已讲)。
void test02() { QString str1("/home/rocky/work/code.my.txt"); QString seq('/'); qsizetype last_pos = str1.lastIndexOf(seq); QString filename = str1.mid(last_pos + seq.size()); // "code.my.txt" QString postfix = str1.mid(str1.lastIndexOf('.') + 1); // "txt" QString pathname = str1.left(last_pos + seq.size()); // "/home/rocky/work/" qDebug() << filename << postfix << pathname;}- 这是路径解析经典套路:
lastIndexOf('/')找最后一级分隔符,mid切文件名,lastIndexOf('.')切后缀,left取目录。seq.size()对QString("/")是 1(一个字符),不能用strlen思维。
void test03() { QString str1("Content-Type: image/png\r\nContent-length: 34789\r\n"); QString key = "Content-length:"; qsizetype start_pos = str1.indexOf(key); qsizetype end_pos = str1.indexOf("\r\n", start_pos + key.size()); QString len_str = str1.mid(start_pos + key.size(), end_pos - (start_pos + key.size())); bool flags = false; int len = len_str.toInt(&flags); // 字符串转整数 if (flags) qDebug() << len; // 34789}- 这是从文本报文中提取 K-V 值的通用模式:先
indexOf定位键,再indexOf定位值结束符,用mid(pos, len)截取,toInt转换并靠bool*判断成败。注意mid第二参是长度(不是结束索引),与last_pos套路里的单参mid不同。
void test04() { QMap<QString, int> res2; res2.insert("a1", 10); res2["w1"] = 30; res2["b1"] = 20; for (auto x: res2.keys()) { // keys() 返回有序键列表 qDebug() << x << ": " << res2[x]; }}QMap演示两种写入(insert与[])和遍历。keys()返回按 key 排序的列表(QMap 有序),所以输出顺序为a1, b1, w1而非插入顺序——这是QMap与QHash的关键区别。
💡 Tips:这段代码藏着的细节——①
str1.insert(1,"们")的索引基于字符而非字节,对中文安全;②test03的mid(start+key.size(), len)第二参必须是长度,写成mid(start, end)会多取;③QMap::keys()遍历时若在大循环里高频调用有拷贝开销,生产环境更推荐for (auto it = res2.cbegin(); ...)或 C++11 范围 for 直接遍历res2(得键值对)。
5. 运行效果 / 验证
编译运行 qt10_tst-char,预期输出:
test01:依次打印hello、你好,hello_!!、你们好,hello_!!。test02:code.my.txttxt/home/rocky/work/。test03:打印34789(从报文正确提取整数)。test04:按a1: 10b1: 20w1: 30顺序输出(注意是按键排序,非插入序)。
💡 Tips:验证时最容易误判”成功”的假象——只看到有输出就以为算法对。应单独核对每步索引:比如
test02的pathname末尾是否带/、test03的len是不是恰好 34789 而非 3478 或带空格。字符串切分差一个索引,值就错一位,肉眼难辨,务必逐项核对边界。
容易踩坑(注意点)
- 坑 1:把
QString::length()当字节数。现象:中文长度计算错误。原因:它返回字符数(UTF-16)。规避:要字节数用toUtf8().size()。 - 坑 2:
mid(pos, n)第二参是长度不是结束索引。现象:截多了/截少了。原因:与某些语言的slice(start,end)语义不同。规避:牢记n是长度;单参mid(pos)才是截到末尾。 - 坑 3:
QMap误当插入序。现象:遍历顺序和插入不一致。原因:QMap 按键有序。规避:要保序用QList<QPair>或 Qt6 的QKeyValueIterator配合QHash+顺序列表。 - 坑 4:用
char*字面量拼接std::string后直接喂 QString 却乱码。现象:中文乱。原因:源文件非 UTF-8(qt09 坑 1)。规避:源码存 UTF-8,或fromLocal8Bit。 - 坑 5:循环里反复
QString::mid大字符串。现象:性能下降。原因:每次 mid 都拷贝子串。规避:Qt6 用QStringView零拷贝切片,或先const QChar*。 - 坑 6:
toInt(&ok)不看 ok 直接信任结果。现象:非法字符串得到 0 误当真值。原因:toInt失败返回 0。规避:永远检查bool ok。
学习要点总结
核心知识点
char/char*/std::string都是字节层,无编码;QString是 UTF-16 Unicode 层。- QString 增删改查:
append/prepend/insert/mid/left/indexOf/lastIndexOf,索引按字符。 - 文本解析套路:
lastIndexOf找分隔 →mid切片 →toInt/toDouble转值(检查ok)。 QMap有序关联容器,遍历按键排序;与QHash按场景取舍。
关键 API
QString::mid/left/right:子串提取(长度语义)。QString::indexOf/lastIndexOf:定位(字符位置)。QString::toInt/toDouble:带bool*成败判断的转换。QMap::insert/operator[]/keys:有序键值存储。
下一步学习
- qt09 编码转换:本章的 char/string 是它底层;两者对照读。
- qt11 布局:按钮文案、文本编辑都用到 QString 操作。
- qt22 SQLite:文本字段存取依赖本章字符串处理 + qt09 编码。
💡 Tips:本篇直接挂钩 qt09(编码转换的底层铺垫) 与 qt11(UI 字符串)。口诀:“索引按字符、mid 是长度、toInt 看 ok、QMap 有序存。“
Q&A(常见问题)
Q1:QString::length() 和 toUtf8().size() 有什么区别? A1:length() 返回 UTF-16 字符数(中文算 1 个),toUtf8().size() 返回 UTF-8 字节数(中文通常 3 字节)。例如 QString("你好").length() 是 2,.toUtf8().size() 是 6。需要”网络/文件字节数”用后者,需要”显示字符数”用前者。
Q2:mid(pos, n) 和 mid(pos) 怎么用才不会错? A2:mid(pos, n) 从 pos 取 n 个字符;mid(pos) 从 pos 取到末尾。常见错误是把第二参写成”结束索引”,正确应是”长度”。例如取 str 第 3 到第 5 字符(含)应写 str.mid(2, 3)(起始索引 2、长度 3),不是 mid(2,5)。
Q3:Qt5/Qt6 在字符串/容器上有差异吗? A3:QString 索引类型由 int 变为 qsizetype(Qt6),QStringRef 被 QStringView 取代(零拷贝视图);QMap/QHash 接口基本兼容。Qt6 推荐用 QStringView 替代临时 mid 拷贝做切片,减少内存分配。其余增删改查语义两版一致。
Tips(全局速记)
最容易忽视的细节
char*字面量进 QString 默认 UTF-8——源文件必须 UTF-8(与 qt09 一致),否则中文构造即乱。toInt(&ok)的 ok 必须查——失败返回 0,别把 0 当真实值。QMap遍历是按键排序的,不是插入顺序;要保序换容器。
踩坑记录
- “有输出就等于解析对”的错觉:test03 若
mid长度算错,可能只差一两位(如 3478 而非 34789),打印出来像”成功了”。务必把提取结果与预期值逐字符比对。 - 频繁
mid大字符串的性能坑:在解析大日志/大报文时,每行都mid拷贝子串会大量分配内存;Qt6 用QStringView零拷贝可显著提速。
与后续章节的衔接
- qt09:char/编码基础在此,转换 API 在彼,互为表里。
- qt11 布局:
QTextEdit、QLineEdit内容都是 QString,增删改查同源。 - qt22 SQLite:SQL 文本拼接、字段解析都依赖本章字符串手法 + qt09 编码。
设计决策思考
- 为什么 Qt 自己造
QMap而不是全用std::map? Qt 容器与QObject、信号槽、元对象系统深度集成(如QVariant可存QMap),且历史早于标准库成熟;Qt6 仍保留是为兼容海量既有代码。新项目也可混用std::map,但跨 Qt API 边界(如QVariant)仍以 Qt 容器为主。 - 为什么 QString 的
mid返回拷贝而非视图? 早期设计保证返回的 QString 独立、可安全修改;代价是大字符串切片有拷贝成本。Qt6 引入QStringView/midView补齐零拷贝能力,兼顾历史兼容与新性能需求。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
评论区
正在加载 GitHub 评论…