【问题标题】:C++ Correctly read files whose Unicode characters might be larger than a byteC++ 正确读取 Unicode 字符可能大于一个字节的文件
【发布时间】:2017-07-13 05:57:30
【问题描述】:

我现在花了很多时间阅读有关 Unicode、它的编码和许多相关主题的信息。
我研究的原因是因为我试图读取文件的内容并逐个字符地解析它们。

如有错误请指正:

  • C++ 的getc() 返回一个int,它可能等于EOF
    如果 返回值不等于EOF 它可以被解释为 一个安全地分配给char
    因为std::string是 基于char,我们可以使用这些字符构建std::strings 并使用 那些。

我有 C# 背景,我们使用 C# 的 char(16 位)作为 strings。
这些chars 的值直接映射到 unicode 值。
值为5char 等于位于U+0005 的Unicode 字符。

我不明白如何在 C++ 中读取包含值可能大于一个字节的字符的文件。当我只能读取值限制为一个字节的字符时,我对使用 getc() 感到不舒服。

我可能遗漏了有关如何使用 C++ 正确读取文件的要点。
非常感谢任何见解。

我正在使用 VC++ 运行 Windows 10 x64。
但如果可能的话,我更愿意让这个问题与平台无关。


编辑

我想强调一下 Klitos Kyriacou 在 cmets 中链接的堆栈溢出帖子:
How well is Unicode supported in C++11?

简要介绍了 C++ 对 Unicode 的支持程度。
有关更多详细信息,您应该阅读/观看已接受答案中提供的资源。

【问题讨论】:

  • 您有机会查看std::wstring 和/或wchar_t吗?
  • 您希望使用什么编码?
  • @VadaPoché 让我读一下这些东西......
  • 而 C#(以及 Java)在读/写操作期间会自动进行编码/解码,而在 C++ 中,您必须将字节读取为字节,然后使用 std::codecvt。另见问题How well is Unicode supported in C++11?
  • 这些字符的值直接映射到 unicode 值。 - 您知道 16 位数据类型不能容纳完整的 unicode 范围吗?

标签: c++ string unicode


【解决方案1】:

情况是 C 的 getc() 是在 1970 年代编写的。就所有意图和目的而言,它的意思是“读取一个八位字节”,而不是“读取一个字符”。几乎所有的二进制数据都建立在八位字节上。

Unicode 允许字符超出八位字节可以表示的范围。因此,Unicode 人天真地提出了 16 位字符的标准。微软随后很早就采纳了该提案,并在 Windows 中添加了宽字符(wchar_t 等)。一个问题是 16 位不足以代表具有某种状态的每种人类语言中的每个字形,另一个问题是二进制文件的字节顺序。所以 Unicode 的人不得不添加一个 32 位的 unicode 标准,然后他们在 Unicode 文件的开头加入了一点 enianness 和格式标签。最后,16 位 Unicode 字形与 Microsoft 的 wchar_t 字形不太匹配。

所以结果是一团糟。以完全的准确性和可移植性读取和显示 16 位或 32 位 Unicode 文件是相当困难的。此外,很多程序仍在使用 8 位 ascii。

幸运的是,UTF-8 被发明了。 UTF-8 向后兼容 7 位 ascii。如果设置了最高位,则字形由多个字符编码,并且有一个方案可以告诉您有多少。 nul 字节从不出现,除非作为字符串结束指示符。因此,大多数程序都会正确处理 UTF-8,除非它们尝试拆分字符串或以其他方式尝试将它们视为英语。

由于可变长度规则,UTF-8 的缺点是无法随机访问字符。但这是一个小缺点。通常 UTF-8 是保存 Unicode 文本并在程序中传递它的方法,并且只有在实际需要字形时才应将其分解为 Unicode 代码点,例如用于展示目的。

【讨论】:

  • +1 用于提供历史记录。还应该说 UTF-16 非常容易出错,因为即使开发人员不知道代理对,它也可以在 99% 的时间内工作,因为这些开发人员最喜欢只使用 UCS-2 范围内的代码点进行测试.
  • UTF-8 的缺点是无法随机访问字符 ...对于 UTF-16 甚至 UTF-32 也是如此,因为 @987654321 @ 可以由多个 unicode 字符组成。
【解决方案2】:

与 Windows API 兼容的 16 位“字符”的等效项是 wchar_t。请注意,虽然 wchar_t 在某些平台上可能是 32 位的,但如果您想以独立于平台的方式存储 UTF-16 编码的字符串,请使用 char16_t。

如果您在 Windows 平台上使用 char16_t,则在将字符串传递给 OS API 时必须进行一些强制转换。

等价的字符串类型有:

  • std::wstring (wchar_t)
  • std::u16string (char16_t)

文件流类型:

  • std::wifstreamstd::basic_ifstream<wchar_t> 的类型定义)
  • std::basic_ifstream<char16_t>
  • std::wofstreamstd::basic_ofstream<wchar_t> 的类型定义)
  • std::basic_ofstream<char16_t>

将 UTF-8 编码文件读入 UTF-16 字符串的示例:

#include <windows.h>
#include <fstream>
#include <string>
#include <locale>
#include <codecvt>

int main()
{   
    std::wifstream file( L"test_utf8.txt" );

    // Apply a locale to read UTF-8 file, skip the BOM if present and convert to UTF-16.
    file.imbue( std::locale( file.getloc(),
        new std::codecvt_utf8_utf16<wchar_t, 0x10ffff, std::consume_header> ) );

    std::wstring str;
    std::getline( file, str );

    ::MessageBox( 0, str.data(), L"test", 0 );

    return 0;
}

如何将 UTF-16 编码文件读入 16 位 std::wstringstd::u16string

显然这并不容易。 有 std::codecvt_utf16 但当与 16 位 wchar_t 字符类型一起使用时,它会产生 UCS-2 这只是 UTF-16 的一个子集,因此无法正确读取代理对。见cppreference example

我不知道 C++ ISO 委员会是如何做出这个决定的,因为它在实践中完全没有用。至少他们应该提供一个标志,这样我们就可以选择是要限制自己使用 UCS-2 还是要读取完整的 UTF-16 范围。

也许还有其他解决方案,但我现在不知道。

【讨论】:

  • +1 有多种原因:指出我现在认为不应将其用于 UTF8 的 wstring 和 wchar_t,描述不同的文件流并为 UTF8 和 UTF16 提供示例。我不接受这个作为答案,因为我相信像 utfcpp(Trevor 提到的)这样的库可以比我能理解/编写的任何示例更好地处理验证和解码。不过,如果有人正在寻找自己编写的解决方案,这可能是要走的路。感谢 zett42 的信息 :)
  • 不幸的是,我不得不删除 UTF-16 示例,因为它只读取 UCS-2(见上文)。应该更仔细地阅读文档...
  • 只读取二进制文件。使用标准库的机器可以轻松工作,在其他地方做一些令人讨厌的事情。过去,我的“做其他事情”包括从头开始编写 UTF-8 编解码器,但现在使用 C++11 及更高版本的库在这方面还不够糟糕,无法证明努力的合理性。
  • 它提供了 UCS-2,因为这是它面对 wchar_t 为 16 位的实现所能做的一切。 C++ 标准要求 wchar_t 对于每个代码点都足够大,这意味着 32 位(截至 1996 年)。
【解决方案3】:

我建议观看Unicode in C++ by James McNellis
这将有助于解释 C++ 在处理 Unicode 时具有和不具有哪些便利。
您会发现 C++ 缺乏对轻松使用 UTF8 的良好支持。

因为听起来您想遍历每个字形(不仅仅是代码点),
我建议使用 3rd pary 库来处理错综复杂的问题。
utfcpp 对我来说效果很好。

【讨论】:

  • 您链接的演讲提供了一些关于 c++ 中 Unicode 支持的重要见解。我可以将它推荐给任何想要更好地理解一般字符编码的人(不仅仅是 C++)。我将使用 utfcpp,因为据我所知,它似乎为 1)验证和 2)转换提供了最佳功能。
猜你喜欢
  • 2017-02-28
  • 2012-02-04
  • 2014-10-11
  • 1970-01-01
  • 1970-01-01
  • 2019-03-18
  • 1970-01-01
  • 2018-06-22
相关资源
最近更新 更多