字符串数据类
概述
本页概述了 Qt 中的字符串类,特别是大量字符串容器,以及如何在性能关键型代码中高效使用它们。
以下关于如何高效使用的说明针对的是有经验的开发人员,他们正在开发包含大量字符串处理的性能关键型代码。例如,解析器或文本文件生成器。一般来说,QString 可以随处使用,而且性能良好。它还提供了处理多种编码的 API(例如QString::fromLatin1() )。对于许多应用程序,尤其是当字符串处理对性能的影响不大时,QString 将是一个简单而充分的解决方案。一些 Qt XML 函数会返回一个QStringView 。如果需要,可以使用QStringView::toString() 将其转换为QString 。
有影响的提示
以下三条规则可在不增加太多复杂性的情况下大幅改进字符串处理。遵循这些规则可在大多数情况下获得接近最佳的性能。前两条规则涉及字符串字面量的编码和在源代码中标记字符串字面量。第三条规则涉及使用部分字符串时的深度复制。
- 所有只包含 ASCII 字符的字符串(例如日志信息)都可以使用 Latin-1 编码。使用string literal
"foo"_L1
。如果没有这个后缀,源代码中的字符串字面量会被假定为 UTF-8 编码,处理速度会变慢。一般来说,尽量使用最严格的编码,在很多情况下都是 Latin-1。 - 用户可见字符串通常会被翻译,并通过QObject::tr() 函数传递。该函数接收字符串字面量(const char 数组),并按照所有用户界面元素的要求返回带有 UTF-16 编码的QString 。如果不使用翻译基础结构,则应在整个应用程序中使用 UTF-16 编码。使用字符串字面量
u"foo"
创建 UTF-16 字符串字面量,或使用 Qt XML 特有的字面量u"foo"_s
直接创建QString 。 - 在处理QString 的部分内容时,不要将每个部分复制到自己的QString 对象中,而是创建QStringView 对象。这些对象可以使用QStringView::toString() 转换回QString ,但应尽量避免这样做。如果函数返回QStringView ,那么尽可能继续使用该类是最有效的做法。API 类似于常量QString 。
高效使用
要高效地使用字符串类,应了解以下三个概念:
- 编码
- 拥有容器和非拥有容器
- 字面
编码
编码方面,Qt 以某种形式支持 UTF-16、UTF-8、Latin-1(ISO 8859-1)和 US-ASCII(即 Latin-1 和 UTF-8 的通用子集)。
- Latin-1 是一种字符编码,每个字符使用一个字节,这使它成为最高效但也是最有限的编码。
- UTF-8 是一种可变长度字符编码,使用一至四个字节对所有字符进行编码。它向后兼容 US-ASCII,是源代码和类似文件的常用编码。Qt 假定源代码使用 UTF-8 编码。
- UTF-16 是一种可变长度编码,每个字符使用两个或四个字节。它是 Qt 中用户公开文本的常用编码。
更多信息,请参阅Qt 中的 Unicode 支持信息。
其他编码以单个函数(如QString::fromUcs4() 或QStringConverter 类)的形式提供支持。此外,Qt 还提供了一个与编码无关的数据容器QByteArray ,该容器非常适合存储二进制数据。QAnyStringView 会跟踪底层字符串的编码,因此可将视图带入具有任何受支持编码标准的字符串。
不同编码之间的转换成本很高,因此应尽量避免。另一方面,更紧凑的编码(尤其是字符串字面量)可以减少二进制文件的大小,从而提高性能。如果字符串字面量可以用 Latin-1 表示,那么即使在某些时候必须转换为 UTF-16,它也能在这些相互竞争的因素之间找到一个很好的折衷方案。当必须将 Latin-1 字符串转换为QString 时,可以相对高效地完成转换。
功能
字符串类可根据其支持的功能进一步区分。其中一个主要区别是,字符串类是拥有并控制其数据,还是仅仅引用其他地方的数据。前者称为拥有容器,后者称为非拥有容器或视图。非自有容器类型通常只记录一个指向数据起始位置及其大小的指针,因此轻便而廉价,但只要数据仍然可用,它就一直有效。所有者字符串会管理存储数据的内存,确保数据在容器的整个生命周期内都是可用的,但其创建和销毁会产生分配和释放内存的成本。视图通常支持所有者字符串功能的子集,但无法修改底层数据。
因此,字符串视图特别适用于表示较大字符串的部分内容,例如在解析器中,而所有者字符串则适用于持久存储,例如类的成员。如果函数返回的字符串是通过组合片段等方式构建的,那么它必须返回一个所有者字符串;但如果函数返回的是某个持久存储字符串的一部分,那么视图通常更为合适。
请注意,Qt 中的所有者容器隐式共享数据,这意味着通过值传递或返回大型容器也很有效,尽管由于引用计数的原因,效率略低于通过引用传递。如果想利用 Qt 类的隐式数据共享机制,就必须将字符串作为拥有容器或对拥有容器的引用来传递。转换为视图和返回视图总是会创建一个额外的数据副本。
最后,Qt 提供了用于单字符、字符串列表和字符串匹配器的类。这些类适用于 Qt 支持的大多数编码标准,但也有一些例外。更高级的功能由专门的类提供,如QLocale 或QTextBoundaryFinder 。这些高级类通常依赖QString 及其 UTF-16 编码。有些类是模板,可与所有可用的字符串类一起使用。
字面量
C++ 标准提供了字符串字面量,用于在编译时创建字符串。字符串字面量有两种,一种是由语言定义的字符串字面量,另一种是由 Qt 定义的字面量,即所谓的用户自定义字面量。C++ 定义的字符串字面量用双引号括起来,可以有一个前缀,告诉编译器如何解释其内容。对于 Qt XML,UTF-16 字符串字面u"foo"
是最重要的。它可以在编译时创建一个以 UTF-16 编码的字符串,从而省去了在运行时从其他编码进行转换的麻烦。QStringView 可以轻松有效地从一个字符串中构造出来,因此可以将其传递给接受QStringView 参数的函数(或结果是QAnyStringView )。
用户定义的字面形式与 C++ 定义的字面形式相同,但在结尾引号后增加了一个后缀。编码仍由前缀决定,但产生的字面量将用于构造某个用户定义类型的对象。因此,Qt XML 为它自己的一些字符串类型定义了这些字符串类型:u"foo"_s
(用于QString )、"foo"_L1
(用于QLatin1StringView )和u"foo"_ba
(用于QByteArray )。这些都是通过使用StringLiterals Namespace 提供的。纯 C++ 字符串字面"foo"
将被理解为 UTF-8,而转换为QString 以及 UTF-16 将非常昂贵。如果有纯 ASCII 格式的字符串字面量,可使用"foo"_L1
将其解释为 Latin-1,从而获得上述各种好处。
基本字符串类
下表概述了各种文本编码标准的基本字符串类。
编码 | C++ 字符串字面 | Qt 用户定义字面量 | C++ 字符 | Qt 字符 | 自有字符串 | 非所有字符串 |
---|---|---|---|---|---|---|
拉丁-1 | - | ""_L1 | - | QLatin1Char | - | QLatin1StringView |
UTF-8 | u8"" | - | char8_t | - | - | QUtf8StringView |
UTF-16 | u"" | u""_s | char16_t | QChar | QString | QStringView |
二进制/无 | - | ""_ba | std::byte | - | QByteArray | QByteArrayView |
灵活 | 任何 | - | - | - | - | QAnyStringView |
一些缺失的条目可以用内置和标准库 C++ 类型代替:拥有的 Latin-1 或 UTF-8 编码字符串可以是std::string
或任何 8 位char
数组。QStringView 也可以引用任何 16 位字符数组,例如某些平台上的 std::u16string 或 std::wstring。
Qt XML 还为其中一些类型提供了专门的列表,即QStringList 和QByteArrayView ,以及匹配器QLatin1StringMatcher 和QByteArrayMatcher 。匹配器也有在编译时创建的静态版本,QStaticLatin1StringMatcher 和QStaticByteArrayMatcher 。
更值得注意的是
- QStringLiteral 是一个宏,与 完全相同,但没有 。最好使用现代字符串字面。
u"foo"_s
StringLiterals Namespace - QLatin1String 是 的同义词,用于向后兼容。它不是一个自有字符串,在未来的版本中可能会被删除。QLatin1StringView
- QAnyStringView 为具有三种支持编码中任何一种编码的字符串提供视图。编码与数据引用一起存储。该类非常适合创建可使用多种字符串类型和编码的接口。与其他类不同,该类不直接对 进行处理。处理是在相应编码的底层 、 或 上进行的。在将该类作为参数的函数中,请使用 () 进行同样的处理。QAnyStringView QLatin1StringView QUtf8StringView QStringView QAnyStringView::visit
- 在 UTF-8 编码的源代码文件中,含有非 ASCII 字符的QLatin1StringView 并不能直接构建,需要特殊处理,请参见QLatin1StringView 文档。
- QStringRef 是对 部分的引用,可在 Qt5Compat 模块中使用,以实现向后兼容。应替换为 。QString QStringView
与字符串相关的高级类
更多提供额外功能的高级类主要与QString 以及 UTF-16 一起工作。它们是
- QRegularExpression QRegularExpressionMatch 和 用于模式匹配和正则表达式。QRegularExpressionMatchIterator
- QLocale 用于以适合用户语言和文化的方式将数字和数据转换为字符串或从字符串转换为数字和数据。
- QCollator 和 比较字符串与用户语言、脚本或地域的关系。QCollatorSortKey
- QTextBoundaryFinder 按照 Unicode 规则分割文本,以便排版。
QStringBuilder
在+
操作符下,一个内部类将大大提高字符串连接的性能,请参见QString 文档。
有些类是模板或具有灵活的 API,可与各种字符串类一起使用。这些类是
- QTextStream 以流式输入 , 或QIODevice QByteArray QString
- QStringTokenizer 分割字符串
使用哪个字符串类?
使用字符串类的一般指导原则是
- 避免复制和内存分配、
- 避免编码转换,以及
- 选择最紧凑的编码。
Qt 提供了许多避免内存分配的功能。大多数 Qt 容器采用隐式共享数据。要实现隐式共享,必须有一个不间断的同类链--从QString 转换到QStringView 再转换回来将导致两个QStrings 不能共享它们的数据。因此,函数需要以QString 的形式传递数据(值或引用均可)。隐式数据共享无法提取字符串的部分内容。要使用较长字符串的部分内容,可以使用字符串视图这种显式数据共享形式。
坚持使用某种编码可以减少编码间的转换。例如,如果不需要转换成任何其他编码,以 UTF-8 编码接收的数据最好以 UTF-8 编码存储和处理。相同编码的字符串之间的比较速度最快,大多数其他操作也是如此。如果需要经常比较某种编码的字符串或将其转换为其他编码,那么转换并存储一次可能会比较好。某些操作提供了许多重载(或QAnyStringView 重载)来处理各种字符串类型和编码,如果使用相同的编码不可行,则应将它们作为优化性能的第二选择。在调用函数之前进行明确的编码转换,应该是在没有其他选择的情况下的最后手段。Latin-1 是一种非常简单的编码,Latin-1 和任何其他编码之间的操作几乎与相同编码之间的操作一样高效。
在没有其他限制条件的情况下,应选择效率最高的编码(从效率最高到最低依次为 Latin-1、UTF-8、UTF-16)。对于错误处理和日志记录,QLatin1StringView 通常就足够了。Qt XML 中的用户可见字符串始终是QString 类型,因此采用 UTF-16 编码。因此,在用户可见字符串的整个生命周期中,使用QStrings 、QStringViews 和QStringLiterals 是最有效的。QObject::tr() 函数提供了正确的编码和类型。如果编码不起作用,例如用于存储二进制数据,或者编码未知,则应使用QByteArray 。
用于创建 API 的字符串类
成员变量
几乎在所有情况下,成员变量都应该是自有类型。只有当引用的所有者字符串的生命周期保证超过对象的生命周期时,视图才能用作成员变量。
函数参数
在大多数情况下,函数参数应为适当编码的字符串视图。QAnyStringView 可用作参数,以支持一种以上的编码,而QAnyStringView::visit() 则可在内部用于分叉到每种编码的函数中。如果函数仅限于一种编码,则应使用QLatin1StringView,QUtf8StringView,QStringView 或QByteArrayView 。
如果函数将参数保存在一个所有者字符串中(通常是一个 setter 函数),那么使用相同的所有者字符串作为函数参数是最有效的,这样可以利用 Qt 的隐式数据共享功能。所有者字符串可以作为const
引用传递。使用多个拥有和非拥有字符串类型重载函数会导致重载歧义,应避免使用。Qt 中的所有者字符串类型可自动转换为其非所有者版本,或转换为QAnyStringView 。
返回值
临时字符串必须作为自有字符串返回,通常是QString 。如果返回的字符串在编译时已知,可使用u"foo"_s
在编译时构建QString 结构。如果从函数(例如 getter 函数)中完整返回现有的所有者字符串(例如QString ),则通过引用返回是最有效的方法。它们也可以通过值返回,以便将来返回临时字符串。Qt 使用隐式共享避免了按值返回时分配和复制对性能的影响。
现有字符串的部分内容可以通过适当编码的字符串视图高效地返回,例如QRegularExpressionMatch::capturedView() 返回QStringView 。
使用 API 的字符串类
要高效使用 Qt API,应尽量匹配函数参数类型。如果您的选择有限,Qt 会进行各种转换:所有者字符串会隐式转换为非所有者字符串,非所有者字符串可以创建其所有者对应部分,例如QStringView::toString() 。编码转换在很多情况下都是隐式进行的,但应尽可能避免。为避免从 UTF-8 意外隐式转换,可以激活宏QT_NO_CAST_FROM_ASCII 。
如果需要在运行时组装字符串,然后再将其传递给函数,则需要一个所有者字符串,因此需要QString 。如果函数参数是QStringView 或QAnyStringView ,它将被隐式转换。
如果字符串在编译时已知,则有优化的余地。如果函数接受QString ,则应使用u"foo"_s
或QStringLiteral 宏创建。如果函数期望使用QStringView ,则最好使用普通 UTF-16 字符串字面u"foo"
,如果期望使用QLatin1StringView ,则使用"foo"_L1
。如果可以在两者之间进行选择,例如,如果函数期望使用QAnyStringView ,则使用最严格的编码,通常是 Latin-1。
所有字符串相关类的列表
使用 QString API 的只读子集统一查看 Latin-1、UTF-8 或 UTF-16 字符串 | |
字节数组 | |
字节数组列表 | |
保存可在字节数组中快速匹配的字节序列 | |
使用 QByteArray API 的只读子集查看字节数组 | |
16 位 Unicode 字符 | |
根据本地化整理算法比较字符串 | |
可用于加快字符串校对速度 | |
8 位 ASCII/Latin-1 字符 | |
优化搜索 Latin-1 文本中的子串 | |
对 US-ASCII/Latin-1 编码字符串字面进行精简封装 | |
在各种语言的数字及其字符串表示之间进行转换 | |
使用正则表达式进行模式匹配 | |
正则表达式与字符串的匹配结果 | |
QRegularExpression 对象与字符串全局匹配结果的迭代器 | |
QByteArrayMatcher 的编译时版本 | |
QLatin1StringMatcher 的编译时版本 | |
统一字符编码字符串 | |
编码和解码文本的基类 | |
基于状态的文本解码器 | |
基于状态的文本编码器 | |
字符串列表 | |
保存可在 Unicode 字符串中快速匹配的字符序列 | |
QString 子串的精简包装器 | |
根据给定的分隔符将字符串分割成标记 | |
通过 QString API 的只读子集统一查看 UTF-16 字符串 | |
在字符串中查找 Unicode 文本边界的方法 | |
读写文本的便捷接口 | |
使用 QString API 的只读子集统一查看 UTF-8 字符串 |
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.