如何比较字符(char
)或字符串(char*
,std::string
,std::wstring
,等等),这是在不同的运行环境中可以安全地进行本地化和改变字符编码?比较字符串/字符在本地化与相应的文字这样的字符串/字符用C字面++安全的方式
让我们以下面的最小例子作为开始。
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的strtok
,boost::tokenize
),但基本上他们都做什么strtok
。他们采用一个输入字符串和一串字符作为分隔符号。但是,这些函数不能在字符上工作,而是在字节上工作。
让我们这个简单的例子
strtok("alice, bob; charlie", ",;");
意向的第一个字符串应该被拆分无论是“”或‘’。此外,让我们假设通过一些未知的奇迹,两个字符串幸运地由相同的字符编码UTF-16编码。虽然两个字符串具有相同的编码,但结果是总损失,因为",;"
是四字节序列"\x00\x2c\x00\x3b"
,第一个字符串是40字节序列,每个第二个字节为'\x00'
。因为strtok
(和boost::tokenize
和其他)在字节上工作,结果是伪造的。
我知道还有std::wstring
,因为C++ 11还有std::u16string
和std::u32string
,但它们并不是真正的救援。 (我不想详细说明它们,因为这个问题已经足够长了。)
当然,像IBM的ICU或像Qt这样的完整框架等第三方库可以避免所有这些问题,但所有这些库都解决了这些问题通过定义自己的字符串类来解决问题。
一方面,这些库大多互相不兼容,或者如果想要合并这些库,必须进行大量的类型转换和字符串复制。另一方面,我通常只写小命令行工具,我不想创建一个像Qt这样的真正庞大的库的依赖,只是为了完成像这个问题的第一个例子那样的任务。
我不能相信,像一个字符与'y'
或'n'
比较这样一个琐碎的问题没有“标准”的解决方案,只使用C++标准库。所以回到我原来的问题:
如何比较字符(char
)或字符串(char*
,std::string
,std::wstring
,等)与相应的文字,使得它是安全的定位和不同的字符编码中不同的运行时间环境对其他库的依赖性很小?
“我不能相信,对于比较字符'y'或'n'这样的小问题,没有”标准“的解决方案,只使用C++标准库。” - 不幸的是,情况正是如此。 – dgel