C++ 中的 Unicode 字符串索引

Posted

技术标签:

【中文标题】C++ 中的 Unicode 字符串索引【英文标题】:Unicode string indexing in C++ 【发布时间】:2015-07-17 11:55:23 【问题描述】:

我来自 python,你可以使用 'string[10]' 来按顺序访问一个字符。如果字符串以 Unicode 编码,它将给我预期的结果。但是,当我在 C++ 中对字符串使用索引时,只要字符是 ASCII 它就可以工作,但是当我在字符串中使用 Unicode 字符并使用索引时,在输出中我会得到像 /201 这样的八进制表示。 例如:

string ramp = "ÐðŁłŠšÝýÞþŽž";
cout << ramp << "\n";    
cout << ramp[5] << "\n";

输出:

ÐðŁłŠšÝýÞþŽž
/201

为什么会发生这种情况,如何在字符串表示中访问该字符,或者如何将八进制表示转换为实际字符?

【问题讨论】:

我建议使用std::wstringstd::wcout @CoryKramer 我不会无条件推荐,例如this。我宁愿推荐使用合适的库。 @BaummitAugen 好点。字符编码让我怀疑我对编程的了解:/ C++ 没有任何真正的原生 Unicode 支持。 @Puppy: ICU 有。 C++ 也没有对 GUI 或音频处理的本机支持,但这并没有使它不适合这项工作。 ;-) 【参考方案1】:

标准 C++ 不具备正确处理 Unicode 的能力,会给您带来与您观察到的问题类似的问题。

这里的问题是C++ 早于 Unicode 一个舒适的边缘。这意味着即使您的字符串文字也将以 实现定义 方式解释,因为这些字符未在基本源字符集中定义(基本上,ASCII-7 字符减去 @ 987654325@、$ 和反引号)。

C++98 根本没有提到 Unicode。它提到wchar_twstring 基于它,指定wchar_t 能够“表示当前语言环境中的任何字符”。但这弊大于利...

Microsoft 将wchar_t 定义为 16 位,这对于当时的 Unicode 代码点来说已经足够了。但是,从那时起,Unicode 已扩展到 16 位范围之外......并且 Windows 的 16 位 wchar_t 不再“宽”,因为您需要其中两个来表示 BMP 之外的字符 - 和Microsoft 文档臭名昭著模棱两可,wchar_t 表示 UTF-16(具有代理对的多字节编码)或 UCS-2(宽编码,不支持 BMP 以外的字符)。

一直以来,Linux wchar_t 是 32 位的, 宽度足以支持 UTF-32...

C++11 对该主题进行了重大改进,添加了 char16_tchar32_t 包括它们相关的 string 变体以消除歧义,但它仍然没有完全支持 Unicode 操作.

作为一个例子,尝试转换例如将德语“Fuß”转为大写,您会明白我的意思。 (单个字母'ß' 需要扩展为'SS',这是标准函数——一次处理一个字符输入,一个字符输出——无法做到的。)

但是,there is helpUnicode 国际组件 (ICU) 完全具备在 C++ 中处理 Unicode 的能力。至于在源代码中指定特殊字符,您将不得不使用 u8""u""U"" 来强制将字符串文字分别解释为 UTF-8、UTF-16 和 UTF-32,使用八进制/ 十六进制转义或依赖您的编译器实现来适当地处理非 ASCII-7 编码。

即使这样你也会得到一个整数值std::cout &lt;&lt; ramp[5],因为对于C++,一个字符只是一个具有语义意义的整数。 ICU 的ustream.hicu::UnicodeString 类提供operator&lt;&lt; 重载,但ramp[5] 只是一个16 位无符号整数(1),如果他们的unsigned short 突然被解释为字符,人们会斜视你.为此,您需要 C-API u_fputs() / u_printf() / u_fprintf() 函数。

#include <unicode/unistr.h>
#include <unicode/ustream.h>
#include <unicode/ustdio.h>

#include <iostream>

int main()

    // make sure your source file is UTF-8 encoded...
    icu::UnicodeString ramp( icu::UnicodeString::fromUTF8( "ÐðŁłŠšÝýÞþŽž" ) );
    std::cout << ramp << "\n";
    std::cout << ramp[5] << "\n";
    u_printf( "%C\n", ramp[5] );

使用g++ -std=c++11 testme.cpp -licuio -licuuc编译。

ÐðŁłŠšÝýÞþŽž
353
š

(1) ICU 内部使用 UTF-16,UnicodeString::operator[] 返回代码 unit,而不是代码 point,因此您最终可能会得到一半一个代理对。查找 API docs 以了解索引 unicode 字符串的各种其他方法。

【讨论】:

哪个 ICU 发行版最适合 Mac 中的 Xcode?​​span> Err... 哪个发行版?我不明白你的意思。由于没有适用于 Mac 的二进制包,我认为您采用最新的源版本并编译/安装它? 有二进制发行版可以在不同平台上安装ICU,但我想我会下载源代码。感谢您提供如此详细的答案。有很多信息需要消化。 一些中文和表情符号字符不适合单个 UTF-16 字符。 @RickJames:这就是关于非 BMP 字符和 UTF-16 代理对的部分,是的。即使您使用的是 UTF-32 编码,也有组合字符。【参考方案2】:

C++ 没有有用的本机 Unicode 支持。您几乎肯定需要像 ICU 这样的外部库。

【讨论】:

【参考方案3】:

要单独访问代码点,请使用u32string,它将字符串表示为char32_t 类型的UTF-32 代码单元序列。

u32string ramp = U"ÐðŁłŠšÝýÞþŽž";
cout << ramp << "\n";    
cout << ramp[5] << "\n";

【讨论】:

是的;但你应该提到 C++11 有趣的是 cout &lt;&lt; ramp &lt;&lt; "\n"; 不会与 G++ or Clang++ on coliru 一起编译 @NathanOliver 理所当然地,char32_t 不是 char,这就是 std::cout 的处理方式。 而且由于wcout 处理wchar_t,在Windows 上也不是char32_t,我们可以看到标准C++ 仍然不能很好地处理Unicode。比 C++98 好,但如果你想一路走下去,你仍然需要 ICU。【参考方案4】:

在我看来,最好的解决方案是使用迭代器对字符串执行任何任务。我无法想象一个真的必须索引字符串的场景:如果您需要像示例中的ramp[5] 这样的索引,那么通常会在代码的其他部分计算5,并且通常无论如何,您都会扫描所有前面的字符。这就是标准库在其 API 中使用迭代器的原因。

如果您想获取字符串的大小,也会出现类似的问题。它应该是字符(或代码点)计数还是仅仅是字节数?通常您需要分配缓冲区的大小,因此更需要字节数。您只需非常非常少地需要获取 Unicode 字符数。

如果您想使用迭代器处理 UTF-8 编码的字符串,那么我肯定会推荐 UTF8-CPP。

【讨论】:

【参考方案5】:

回答是怎么回事,cplusplus.com 说的很清楚:

请注意,此类处理字节与使用的编码无关:如果用于处理多字节或可变长度字符序列(例如 UTF-8),则此类的所有成员(例如长度或大小),以及它的迭代器,仍将以字节(不是实际的编码字符)为单位进行操作。

关于解决方案,其他人说得对:ICU 如果您不使用 C++11; u32string 如果你是的话。

【讨论】:

不幸的是,即使u32string 也不是一个完整的答案——而且空间效率也很低。即使 C++11 可用,我也会建议坚持使用 ICU。

以上是关于C++ 中的 Unicode 字符串索引的主要内容,如果未能解决你的问题,请参考以下文章

C++ 中的 Unicode 字符串处理

C++ 中的跨平台字符串(和 Unicode)

在 C++ 中查找和比较 Unicode 字符

c++ 编译器如何从 utf8 源文件生成 unicode 字符串文字

Unicode字符串索引

String.IndexOf 方法笔记