以本地化安全的方式比较C ++中string / char和string / char literal

时间:2015-09-07 07:40:42

标签: c++ character-encoding string-comparison string-literals

如何将字符(char)或字符串(char*std::stringstd::wstring等)与相应的字面值进行比较,以便它是在不同的运行时环境中安全地进行本地化和变化的字符编码?

让我们以下面的最小例子作为开始。

using namespace std;
// Get runtime locale and apply it to i/o streams
locale loc( "" );
cout.imbue( loc );
cin.imbue( loc );

// Ask question and compare answer
char c = '\0';
do {
    cout << "Important question [y/n]" << endl;
    cin >> c;
} while( c != 'n' && c != 'y' );

if( c == 'n' ) {
  // execute 'no'-branch
} else {
  // execute 'yes'-branch
}

(我知道可以通过多种方式改进示例。输入流应该在读取下一个字符之前清除,依此类推。但这不是重点。)

我的问题是,来自环境的角色c会与硬编码的文字'n'进行比较,尽管变量char的类型名称我们不会实际上比较字符(或字形),但在逐位级别上比较单个字节。

在编译期间,文字'n'被转换为执行字符集。如果编译器是Linux下的gcc,则默认为UTF-8。但这并不能保证,因为标准只需要一个涵盖某些字符的代码集。所以实际上每个编译器都可以自由选择合适的字符集。但无论如何,让我们假设编译器暂时将'n'转换为'\x6e'

但是,运行时环境可以使用完全不同的编码。假设环境使用UTF-16。如果用户键入“n”,则输入流将填充两个字节序列"\x00\x6e"。其中,cin >> c读取第一个字节'\x00'并将其与'\x6e'进行比较。显然,这不是预期的目的。

此外,如果我们想将字符串拆分为标记,事情会变得更糟。它有几个函数(C strtokboost::tokenize),但基本上它们都是strtok所做的。它们接受一个输入字符串和一串字符,这些字符将用作分隔符号。但同样,这些函数不能处理字符,而是处理字节。

我们来看这个简单的例子

strtok( "alice, bob; charlie", ",;" );

根据意图,第一个字符串应该分为“,”或“;”。此外,假设通过一些未知的奇迹,两个字符串幸运地由编码UTF-16的相同字符编码。虽然两个字符串具有相同的编码,但结果是完全丢失,因为",;"是四字节序列"\x00\x2c\x00\x3b",第一个字符串是40字节序列,每隔一个字节为'\x00' 。因为strtok(以及boost::tokenize和其他)处理字节,结果是假的。

我知道还有std::wstring,因为C ++ 11还有std::u16stringstd::u32string,但它们并不是真正的救援。 (我不想详细说明它们,因为这个问题已经足够长了。)

当然,有像IBM ICU这样的第三方库或像Qt这样的完整框架可以避免所有这些问题,但所有这些库都通过定义自己的字符串类来解决问题。

一方面,这些库大多彼此不兼容,或者如果想要组合这些库,则必须进行大量的类型转换和字符串复制。另一方面,我通常只编写小的命令行实用程序,我不想像Qt这样真正庞大的库创建依赖,只是为了完成这个问题的第一个例子。

我无法相信,对于像'y''n'这样的琐碎问题而言,没有“标准”解决方案只使用C ++标准库。回到我原来的问题:

如何将字符(char)或字符串(char*std::stringstd::wstring等)与相应的字面值进行比较,以便它是在不同的运行时环境中可以安全地进行本地化和不同的字符编码在必要时对其他库的依赖性很小

1 个答案:

答案 0 :(得分:0)

您在文本模式下打开一个窄字符流,读取一些字符,并将它们与您的文字进行比较。就是这样。它按照定义工作。比较等于'n' 的字符 'n',由您的实现定义。

什么保证您的实现理解的'n'字符是ASCII n或EBCDIC n或其他什么?没有。执行字符集与环境使用的字符集之间的映射是实现定义的。映射可能依赖于语言环境,因此您可以通过设置适当的语言环境在多个映射之间进行选择。您需要查阅实现文档,或者只是盲目地相信映射是由ASCII给出的(下半部分)。幸运的是,理智的实现提供了JustWorks™的映射,而疯狂的实现不会长久存在。

关于使用UTF-16字符串的示例,除非实现承诺其 narrow 字符集将1:1映射到UTF-16或其一部分(在某些语言环境中),否则使用wchar_t和适当的语言环境,或(自C ++ 11)char16_t和u16文字。这基本上就是它们的用途。