【问题标题】:Qt UTF-8 File to std::string Adds extra charactersQt UTF-8 文件到 std::string 添加额外字符
【发布时间】:2019-11-21 05:35:50
【问题描述】:

我有一个 UTF-8 编码的文本文件,其中包含 ²、³、Ç 和 ó 等字符。当我使用以下内容读取文件时,文件似乎被正确读取(至少根据我在查看 contents 变量的内容时在 Visual Studio 的编辑器中看到的内容)

QFile file( filePath );
if ( !file.open( QFile::ReadOnly | QFile::Text ) ) {
    return;
}
QString contents;
QTextStream stream( &file );
contents.append( stream.readAll() );
file.close();

但是,一旦内容转换为std::string,就会添加其他字符。例如,² 被转换为 ²,而它应该只是 ²。这似乎发生在每个非 ANSI 字符上,添加了额外的 Â,这当然意味着当保存新文件时,输出文件中的字符不正确。

当然,我尝试过简单地使用toStdString(),我也尝试过toUtf8,甚至尝试过使用QTextCodec,但都没有给出正确的值。

我不明白为什么从 UTF-8 文件到 QString,然后到 std::string 会丢失 UTF-8 字符。它应该能够重现最初读取的确切文件,还是我完全遗漏了什么?

【问题讨论】:

  • 字符串本身中的多余字符吗?您是否使用调试器检查过?或者您是否仅在打印时看到它(在这种情况下,可能是有缺陷的打印,代码或设置的语言环境或您打印到的终端)。简而言之,您能告诉我们您是如何检查字符串的内容的吗?
  • 当使用 QString 的 TextVisualizer 时,我看到 ²(如预期的那样),当查看 std::string 的 TextVisualizer 时(在调用 toStdString 之后立即)我看到 ²,所以它是在文件被写入之前。我还可以在 ANSI(显示上述字符)的 Notepad++ 和 UTF-8 中查看文件(之前和之后),原始文件显示 ²,新文件显示 ²。看起来有点像 UTF-8 版本正在展示 ANSI 应该是什么。
  • 在谈论字符编码时请不要使用 ANSI,因为没有使用该名称的编码。这只是 Windows 用来表示“根据当前区域设置的代码页”的误称,这导致“ANSI”文本文件几乎总是不能跨不同语言版本的 Windows 移植。
  • 顺便说一句。请发布您正在处理的数据的实际十六进制转储,以及它们在通过QStringstd::string 后的样子。此外,QTextStream 似乎默认准备处理 UTF-16,除非出现 BOM:您是否尝试将文件读取到QByteArray 并使用QString::fromUtf8?这还具有额外的优势,即明确说明您在字符编码方面所做的事情。

标签: c++ string qt utf-8


【解决方案1】:

正如 Daniel Kamil Kozar 在他的回答中提到的那样,QTextStream 没有读取编码,因此实际上并没有正确读取文件。 QTextStream 必须在读取文件之前设置其编解码器,以便正确解析字符。在下面的代码中添加了注释以显示所需的额外文件。

QFile file( filePath );
if ( !file.open( QFile::ReadOnly | QFile::Text ) ) {
    return;
}
QString contents;
QTextStream stream( &file );
stream.setCodec( QTextCodec::codecForName( "UTF-8" ) ); // This is required.
contents.append( stream.readAll() );
file.close();

【讨论】:

    【解决方案2】:

    您所看到的实际上是预期的行为。

    当编码为 UTF-8 时,字符串 ² 由字节 C3 82 C2 B2 组成。假设 QTextStream 实际上正确识别 UTF-8(这并不是那么明显,judging from the documentation,它只在存在 BOM 时提到字符编码检测,并且您没有说任何关于具有 BOM 的输入文件),我们可以假设QTextStream::readAll返回的QString实际上包含字符串²

    QString::toStdString()returns a UTF-8 encoded variant 是给定的QString 所代表的字符串,因此返回值应该包含与输入文件相同的字节——即C3 82 C2 B2

    现在,关于您在调试器中看到的内容:

    1. 您在其中一个 cmets 中声明“QString 在字符串中只有 0xC2 0xB2(这是正确的)。”。这只是部分正确:QString 在内部使用 UTF-16LE,这意味着它的内部字符数组包含两个 16 位值:0x00C2 0x00B2。实际上,当它们被编码为 UTF-16 时,它们映射到字符 ²,这证明了 QString 是根据文件的输入正确构造的。但是,您的调试器似乎足够聪明,可以知道构成 QString 的字节是用 UTF-16 编码的,因此可以正确呈现字符。
    2. 您还声明调试器将从QString::toStdString 返回的std::string 的内容显示为²。假设您的调试器在没有明确说明编码的情况下使用可怕的“ANSI 代码页”将字节解析为字符,并且您使用的英语 Windows 使用 Windows-1252 作为其默认的旧代码页,那么一切都适合:std::string 实际上包含字节C3 82 C2 B2,映射到Windows-1252 中的字符²

    无耻的自我插入:我在去年的一次会议上delivered a talk about character encodings。也许观看它会帮助您更好地理解其中一些问题。

    最后一件事:ANSI 不是编码。根据 Windows 的区域设置,它可能意味着许多不同的编码。

    【讨论】:

    • 嗨丹尼尔,在查看了 QString 的内存转储后,QTextStream 似乎将 UTF-8 字符读取为两个 ASCII 字符。该文件没有 BOM,因为不建议将其用于 UTF-8 文件。 QTextStream 需要在读取文件之前设置其编码。
    • (叹气)它们不可能是 ASCII 字符 ...没关系。
    • 丹尼尔,你能解释一下你的意思吗?也许我只是用错了词? QTextStream 将² 解释为两个单独的字符(不是ASCII)?
    猜你喜欢
    • 2019-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-11
    • 2015-05-14
    • 1970-01-01
    • 2011-03-08
    相关资源
    最近更新 更多