【问题标题】:UTF-8 String IteratorsUTF-8 字符串迭代器
【发布时间】:2012-08-19 05:07:45
【问题描述】:

我正在尝试编写一个支持 Unicode 的跨平台应用程序。我正在使用库 UTF8-C++ (http://utfcpp.sourceforge.net/),但在遍历字符串时遇到问题:

string s1 = "Добрый день";
utf8::iterator<string::iterator> iter(s1.begin(), s1.begin(), s1.end());

for(int i = 0; i < utf8::distance(s1.begin(), s1.end()); i++, ++iter)
{
    cout << (*iter);
}

上述代码在重定向到 UTF-8 格式的文本文件时,会产生以下输出:

6 3 6 3 6 3 6 3 6 3 6 3 3 2 6 3 6 3 6 3 6 3 

如何让s1 的内容正确出现在文件中?

【问题讨论】:

  • 输出中的空格从何而来?你的源文件是什么编码的?您使用的是哪个版本的库?
  • 你真的不想在内部使用 UTF-8。这是一种出色的存储和传输格式,但在代码中使用它确实很痛苦。将其转换为 UTF-32 (Unix) 或 UTF-16 (relay UCS-2) (Win) 并使用固定大小质量。
  • @LokiAstari 和所有其他评论者 - 请阅读:utf8everywhere.org。是的,您确实想在内存中使用 utf8,并且终生忘记任何编码转换......
  • 根据我的经验,多个代码单元是否也构成一个代码点通常是无关紧要的。要么我可以在代码单元方面工作(当规范化不是问题时;复制、连接、简单搜索等)或者我需要知道多个代码点必须被视为一个实体(而不仅仅是为了显示; 用于光标移动、正则表达式、拆分等)。固定宽度编码没有任何价值。 Unicode 字符基本上是可变长度的。
  • “固定宽度格式确实是进行字符串操作时的唯一方法” - 并非如此。请参阅 utf8everywhere 的常见问题解答 18。你会看到固定宽度其实没那么好,可变宽度也不错。

标签: c++ string unicode utf-8 iterator


【解决方案1】:

您需要确保使用正确的数据初始化字符串,然后确保迭代器生成正确的值。

您使用的是 VS2010,所以字符串文字有点问题。 C++ 实现有一个“执行字符集”,它们将来自“源字符集”的字符和字符串文字转换为该字符集。 Visual Studio 不支持 UTF-8 作为执行字符集,因此不会有意生成 UTF-8 编码的字符串文字。

您可以通过欺骗编译器或使用十六进制转义来获得一个。此外,您可以获取包含正确数据的宽字符串,然后在运行时将其转换为 UTF-8,而不是获取 UTF-8 字符串文字。


编辑:Visual Studio 的最新版本现在确实可以获取 UTF-8 字符串文字。 Visual Studio 2015 现在支持 C++11 的 UTF-8 字符串文字。在 Visual Studio 2015 Update 2 中,您还可以使用编译器标志 /execution-charset:utf-8 or /utf-8.


欺骗编译器

如果您将源代码保存为“UTF-8 without signature”,那么编译器会认为源编码是系统区域设置编码。 VS 总是使用系统语言环境编码作为执行编码。因此,当它认为源编码和执行编码相同时,它不会执行任何转换,并且您的源字节(实际上是 UTF-8)将直接用于字符串文字,从而生成 UTF-8 编码的字符串文字。 (请注意,这会破坏对宽字符和字符串文字所做的转换。)

十六进制转义

十六进制转义码允许您手动将任何值的代码单元(在本例中为字节)插入字符串文字。您可以手动确定所需的 UTF-8 编码,然后将这些值插入到字符串文字中。

std::string s1 = "\xd0\x94\xd0\xbe\xd0\xb1\xd1\x80\xd1\x8b\xd0\xb9 \xd0\xb4\xd0\xb5\xd0\xbd\xd1\x8c";

UTF-8 字符串文字前缀

C++11 指定了一个前缀,该前缀创建一个 UTF-8 字符串文字,而不管执行编码如何,但是 Visual Studio 尚未实现这一点。这看起来像:

string s1 = u8"Добрый день";

它要求编译器知道并使用正确的源编码(因此源编码支持所需的字符串)。然后编译器将源编码转换为 UTF-8,而不是执行编码。当 Visual Studio 支持此功能时,您可能希望将源代码保存为“带签名的 UTF-8”。 (同样,VS 依赖于签名来识别 UTF-8 源。)


在你有一个 UTF-8 字符串之后,假设 UTF-8 迭代器工作,你的示例代码应该产生正确的 11 个代码点,我认为输出文本应该如下所示:

104410861073108810991081321076107710851100

插入一些空格使其可读,您可以验证您获得的值是否正确:

1044 1086 1073 1088 1099 1081 32 1076 1077 1085 1100

或将其设为十六进制并添加 Unicode 前缀:

U+0414 U+043e U+0431 U+0440 U+044b U+0439 U+0020 U+0434 U+0435 U+043d U+044c

如果您真的想生成一个 UTF-8 编码的输出文件,那么无论如何您都不应该使用 utf-8 迭代器。

string s1 = "Добрый день";
std::cout << s1;

当输出被重定向到一个文件时,该文件将包含 UTF-8 编码的数据:

Добрый день

我不明白为什么您的实际输出当前包含一堆额外的空格,但看起来正在访问的实际数字是:

63 63 63 63 63 63 32 63 63 63 63

63 是 '?' 的 ASCII 码32是空格的ASCII码; ?????? ????。因此,您显然正在遭受 VC++ 将字符串文字转换为系统区域设置编码的痛苦。

【讨论】:

  • 正如注释 - C++11 在标准 u8"This is UTF-8 string" 中有 UTF-8 文字。
【解决方案2】:

答案已更新。使用 wstring(我认为最好是 VS2010)存储一个 UTF16 字符串,转换为 UTF8,然后输出。

当我在 UTF8 兼容的编辑器 (Scite) 中查看时,这对我有用。

    std::wstring s1 = L"Добрый день";
    std::vector<unsigned char> UTF8;

    utf8::utf16to8( s1.begin(), s1.end(), std::back_inserter( UTF8 ) );

    for( auto It = UTF8.begin() ; It < UTF8.end() ; ++It )
    {
        std::cout << (*It);
    }

我认为 VS2010 中没有办法拥有 UTF8 文字或字符串对象,我认为 UTF16 (wstring) 是您内部最好的选择,然后在导出到文件时使用 UTF8 库与 UTF8 进行转换/网络等

【讨论】:

  • 当我按照您的指示更改程序时,我得到以下输出:2 0 6 2 4 9 6 4 7 5 5 7 3 2 5 2 5 3 6 1 7 6 。此外,我收到了一条警告:warning C4244: 'argument' : conversion from 'wchar_t' to 'utf8::uint8_t', possible loss of data.
  • 宽字符串从不以 UTF8 存储,因此使用 UTF8 迭代器对其进行迭代没有多大意义。
  • 抱歉,我可能误解了这个问题 - 你希望它输出什么?实际的字符串? @MooingDuck 是的,我抓住了错误的问题——他们试图将一个宽字符串存储到一个普通字符串中并得到'?'。
  • 那么你不想使用 UTF8 迭代器,它会得到字符的代码点,而不是通常可以显示的东西。 UTF8 库更多的是用于将 UTF8 转换为普通或宽字符串,然后可以正常显示。
  • @PavelRadzivilovsky 我知道,对!这些天我做了很多 .Net 的东西,而且一直都是 UTF-16!我认为这是最简单的方法,至少在 Windows 上,因为所有 API 都使用它,但似乎几乎所有其他人都不同意 - 所以这是一种学习体验!
猜你喜欢
  • 2011-04-09
  • 2013-06-12
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 2017-03-04
  • 2015-01-19
  • 2017-01-24
相关资源
最近更新 更多