【问题标题】:C++ read and write UTF-32 filesC++ 读写 UTF-32 文件
【发布时间】:2018-10-12 21:07:03
【问题描述】:

我想使用 Visual Studio 2017、C++ 和 WindowsAPI(以前称为 Win32)为自己编写一个语言学习应用程序。操作系统是最新的 Windows 10 内部版本,向后兼容性不是问题。由于我假设英语是用户的母语,而我目前感兴趣的语言是另一种欧洲语言,ASCII 可能就足够了。但我想对它进行未来验证(更多异国情调的语言),我也想尝试使用 UTF-32。我以前使用过 UTF-8 和 UTF-16,但我对后者有更多的经验。

感谢std::basic_string,很容易弄清楚如何获取 UTF-32 字符串:

typedef std::basic_string<char32_t> stringUTF32

由于我为所有 GUI 人员使用 WinAPI,我需要在 UTF-32 和 UTF-16 之间进行一些转换。

现在我的问题是:由于 UTF-32 效率低下而没有被广泛使用,因此网络上几乎没有任何关于它的资料。为了避免不必要的转换,我想将我的词汇表和其他数据保存为 UTF-32(对于所有 UTF-8 倡导者/传播者,替代方案是 UTF-16)。问题是,我找不到如何用 UTF-32 编写和打开文件。

所以我的问题是:如何以 UTF-32 写入/打开文件?我希望不需要第三方库,除非它们是 Windows 的一部分或通常随该操作系统一起提供。

【问题讨论】:

  • "但我希望它能够适应未来(更多异国情调的语言),并且我也想尝试使用 UTF-32。“UTF-8 也不逊色”面向未来”而不是 UTF-32。这就是 UTF 格式的重点:它们都对完全相同的数据范围进行编码。
  • 澄清一下,当您说“写入和打开”文件时,您是在暗指 iostream 样式的格式化 IO,还是仅使用原始数据?
  • @Willi 我问的原因是,归根结底,数据就是数据。除非您尝试使用 STL 的格式化 IO 功能,否则读取和写入文件与编码无关。
  • 另外,你知道,即使在 UTF-32 中,如果你想正确地做事,许多基于文本的操作本质上仍然是多符号的,即使是像搜索字母这样简单的操作,因此相对于 UTF-8,您实际上并没有获得太多收益
  • UTF-8 非常适合用于磁盘存储、网络通信等。UTF-16 在内存使用和逻辑复杂性之间取得了很好的平衡,因为大多数语言不使用 BMP (东亚语言、表情符号等)。在处理 UTF 数据时,您通常必须以 UTF-32 进行处理以进行代码点比较,但您可以在每个代码点的基础上执行此操作而不会浪费内存。将 UTF-32 存储在磁盘上或在内存中使用 UTF-32 字符串几乎没有什么好处。另外,UTF-32 并不能解决“1 个代码点!= 1 个字素”问题——字素是用户倾向于认为的字符。

标签: c++ winapi unicode windows-10 utf-32


【解决方案1】:

我目前感兴趣的是另一种欧洲语言,[so] ASCII 可能就足够了

没有。即使是简单的英语。您知道 Microsoft Word 如何创建“弯引号”吗?这些是非ASCII字符。例如,所有带有重音符号和变音符号的字母。法语或英语是非 ASCII 字符。

我希望它能够适应未来

UTF-8、UTF-16 和 UTF-32 都可以编码每个 Unicode 代码点。它们都是面向未来的。 UTF-32 与其他两个相比没有优势。

也为了未来的证明:我很确定有些脚本使用的字符(技术术语是“字素簇”)由多个代码点组成。粗略搜索出现Playing around with Devanagari characters

UTF-32 的一个缺点是在其他工具中的支持。记事本不会打开您的文件。 Beyond Compare 不会。 Visual Studio 代码……不。 Visual Studio 会,但不会让您创建此类文件。

还有 Win32 API:它有一个函数 MultiByteToWideChar 可以将 UTF-8 转换为 UTF-16(您需要将其传递给所有 Win32 调用),但它不接受 UTF-32。

所以我对这个问题的诚实回答是,不要。否则请按照 Nicol 的回答。

【讨论】:

  • 感谢您的回复,抱歉回复晚了,我很忙。我在发布问题的那天选择了使用 UTF-16。如果只是出于好奇,我会尝试使用 UTF-32,但缺少文档实在是个太大的问题。回到您的答案:我可能不应该编写 ANSII,而是编写 ANSII + Microsoft 扩展或 Windows 在 ANSII 模式下使用的代码页。因此,如果您只需要双语系统,Windows 确实支持足够多的拉丁字母。英语 + 另一种使用拉丁字母的语言。无论如何,这种支持已经足够了。
  • 我的很多应用程序。不过,我主要使用 UNICODE 模式。有些程序我非常关心每个 char == 一个字符,如果有人可以输入他们的法文、德文、俄文或日文名称,我也不会在意。这也是我偶尔会使用旧模式的原因。
【解决方案2】:

如果您有一个char32_t 序列,您可以使用std::basic_ofstream&lt;char32_t&gt;(我将其称为u32_ofstream,但此typedef 不存在)将其写入文件。这与std::ofstream 完全一样,除了它写char32_ts 而不是chars。但也有限制。

大多数具有operator&lt;&lt; 重载的标准库类型都以字符类型为模板。所以他们可以和u32_ofstream一起工作就好了。您将遇到的问题是 user 类型。这些几乎总是假设您正在编写char,因此被定义为ostream &amp;operator&lt;&lt;(ostream &amp;os, ...);。没有转换层的u32_ofstream,这样的流输出就不能工作了。

但您将面临的大问题是字节序问题。 u32_ofstream 会将char32_t 写为您平台的本机字节序。如果您的应用程序通过u32_ifstream 读回它们,那很好。但是,如果其他应用程序读取它们,或者如果您的应用程序需要读取其他人以 UTF-32 编写的内容,这就会成为问题。

典型的解决方案是使用“字节顺序标记”作为文件的第一个字符。 Unicode 甚至为此预留了一个特定的代码点:\U0000FEFF

BOM 的工作方式是这样的。写入文件时,在任何其他代码点之前编写 BOM。

读取未知编码的文件时,您会正常读取第一个代码点。如果它在您的本机编码中等于 BOM,那么您可以正常读取文件的其余部分。如果没有,那么您需要先读取文件并进行字节序转换,然后才能处理它。这个过程看起来有点像这样:

constexpr char32_t native_bom = U'\U0000FEFF';

u32_ifstream is(...);
char32_t bom;
is >> bom;
if(native_bom == bom)
{
  process_stream(is);
}
else
{
  basic_stringstream<char32_t> char_stream
  //Load the rest of `is` and endian-convert it into `char_stream`.
  process_stream(char_stream);
}

【讨论】:

  • 我已经实现了没有此检查的读取功能,因为这对于我的用例并不是绝对必要的(那是在您发布此答案之前)。但我确实认为我应该将其包括在内,以作为良好做法并避免麻烦。尽管稍作改动:如果编码与本机编码不匹配,请跳过该文件并显示错误消息。当我有时间时,我会告诉自己如何进行字节序转换
  • std::basic_ifstream input_stream(filename, std::ios::in);导致错误:严重性代码描述项目文件行抑制状态错误 LNK2001 未解析的外部符号“__declspec(dllimport) public: static class std::locale::id std::codecvt::id”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-06
相关资源
最近更新 更多