为了创建一个在 Windows 和 Linux 之间源代码级可移植并能很好地处理国际化的 C++ 程序,恕我直言,需要考虑三种主要编码:
- C++源代码的编码。
- 外部数据的编码。
- 字符串和文字的编码。
对于 C++ 源代码,实际上没有任何可以替代带有 BOM 的 UTF-8,至少如果标准输入和宽字符串文字应该在 Windows 平台上工作的话。没有 BOM 的 UTF-8 会导致 Microsoft 的 Visual C++ 编译器对源代码采用 Windows ANSI 编码,这对于通过 UTF-8 输出来说很好std::cout
,但在有限程度上有效(Windows 控制台窗口有很多错误)。但是,然后输入viastd::cin
不起作用。
对于外部数据 UTF-8 似乎是这事实上的标准。
但是,内部文字和字符串呢?在这里我有印象窄字符串编码为 UTF-8 是 Linux 中的常见约定。但最近有两个不同的人提出了不同的说法,一个声称 Linux 中国际应用程序中内部字符串的通用约定是 UTF-32,另一个只是声称 Unix 和 Linux 在这方面存在一些未指定的差异。
作为一个出于爱好而稍微摆弄的人,我用一个微型库来抽象出 Windows/Linux 在这一领域的差异,我……必须具体询问
- 在程序中表示字符串的常见 Linux 约定是什么?
我非常确定有一个共同的约定,它非常普遍,以至于这个问题有一个真正的答案™。
一个例子展示了如何以 Linux 的方式反转字符串(直接使用 UTF-8 很复杂,但大概是通过 Linux 中事实上的标准函数来完成的?),也很好,即,作为问题,这个 C++ 程序的 Linux 常规版本是什么(给出的代码适用于 Latin-1 作为 C++ 窄文本执行字符集):
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
#define STATIC_ASSERT( cond ) static_assert( cond, #cond )
int main()
{
string line;
if( getline( cin, line ) )
{
static char const aSingleChar[] = "æ";
STATIC_ASSERT( sizeof( aSingleChar ) - 1 == 1 );
reverse( line.begin(), line.end() );
cout << line << endl;
}
}
答案1
这只是部分答案,因为您的问题相当广泛。
C++ 定义了一种“执行字符集”(事实上,有两种,窄的和宽的)。
当您的源文件包含以下内容时:
char s[] = "Hello";
然后根据执行编码简单地查找字符串文字中字母的数字字节值。 (单独的宽的执行编码适用于分配给宽字符常量的数值L'a'
。)
所有这些都是作为将源代码文件初始读入编译过程的一部分而发生的。一旦进入,C++ 字符只不过是字节,没有附加语义。 (类型名称char
一定是 C 派生语言中最严重的用词不当之一!)
C++11 中有一个部分异常,其中文字u8""
,u""
并U""
确定结果字符串元素的值(即结果值是全局明确且与平台无关的),但这并不影响输入源代码被解释。
一个好的编译器应该允许你指定源代码编码,因此即使您的朋友在 EBCDIC 机器上向您发送她的程序文本,这也不应该成为问题。 GCC 提供以下选项:
-finput-charset
:输入字符集,即源代码文件是如何编码的-fexec-charset
:执行字符集,即如何对字符串文字进行编码-fwide-exec-charset
:宽执行字符集,即如何对宽字符串文字进行编码
GCC 用于iconv()
转换,因此任何支持的编码都iconv()
可以用于这些选项。
我以前写过关于 C++ 标准提供的一些不透明工具来处理文本编码。
示例:采用上面的代码,char s[] = "Hello";
.假设源文件是 ASCII(即输入编码是 ASCII)。然后编译器读取99
,并将其解释为c
,依此类推。当涉及到字面意思时,它读作72
,将其解释为H
。现在,它H
在数组中存储由执行编码确定的字节值(同样72
,如果是 ASCII 或 UTF-8)。当您写入 时\xFF
,编译器会读取99 120 70 70
,将其解码为\xFF
,然后写入255
数组。
答案2
对于外部表示,UTF-8 绝对是标准。一些 8 位编码仍然很强大(主要在欧洲),一些 16 位编码仍然很强大(主要在东亚),但它们显然是传统编码,正在缓慢退出。 UTF-8 不仅是 UNIX 上的标准,也是 Web 上的标准。
对于内部表示,没有这样压倒性的标准。如果您环顾四周,您会发现一些 UTF-8、一些 UCS-2、一些 UTF-16 和一些 UCS-4。
- UTF-8 的优点是它与通用表示形式相匹配,并且它是 ASCII 的超集。特别是,它是这里空字符对应空字节的唯一编码,如果您有 C API(包括 UNIX 系统调用和标准库函数),这一点很重要。
- UCS-2是历史的遗存。它很有吸引力,因为它被认为是一种固定宽度的编码,但它不能代表 Unicode 的全部,这是一个阻碍。
- UTF-16 的主要声誉是 Java 和 Windows API。如果您正在为 Unix 编程,Unix API(喜欢 UTF-8)比 Windows API 更相关。只有旨在与 UTF-16 等 API 交互的程序才会倾向于使用 UTF-16。
- UCS-4 很有吸引力,因为它看起来像一种固定宽度的编码。问题是,事实并非如此。由于字符的组合,不存在固定宽度的 Unicode 编码。
- 还有
wchar_t
。问题是,在某些平台上是 2 个字节,在其他平台上是 4 个字节,并且它表示的字符集没有指定。由于 Unicode 成为事实上的标准字符集,较新的应用程序往往会避开wchar_t
.
在 UNIX 世界中,压倒一切的争论通常是与 UNIX API 的兼容性,指向 UTF-8。然而,它并不通用,因此对于您的库是否需要支持其他编码,没有是或否的答案。
在这方面,unix 变体之间没有区别。Mac OS X 更喜欢分解字符以便获得规范化的表示形式,因此您可能也想这样做:它将在 OSX 上节省一些工作,而在其他 unice 上则无关紧要。
请注意,UTF-8 中没有 BOM 这样的东西。字节顺序标记仅对超字节大小的编码有意义。 UTF-8 编码文件以字符 U+FEFF 开头的要求特定于一些 Microsoft 应用程序。
答案3
有人声称 Linux 国际应用程序中内部字符串的通用约定是 UTF-32
这可能参考了 GCC 定义wchar_t
为 UTF-32 字符的事实,与定义wchar_t
= UTF-16 的 Windows C(++) 编译器不同(为了与 Windows 兼容WCHAR
)。
你可以wchar_t
如果您方便的话,可以在内部使用。然而,它在 *nix 世界中并不像在 Windows 世界中那么常见,因为 POSIX API 从未像 Windows 那样重写为使用宽字符。
在内部使用 UTF-8 对于“编码中立”的例程效果很好。例如,考虑一个将制表符分隔的电子表格转换为 CSV 的程序。您需要特别对待 ASCII 字符\t
、,
、 和"
,但非 ASCII 范围内的任何字节(无论它们代表 ISO-8859-1 字符还是 UTF-8 代码单元)都可以简单地按原样复制。
作为一个出于爱好而稍微摆弄的人,使用一个旨在抽象出 Windows/Linux 在这一领域的差异的微型库,
编写跨平台代码的众多烦恼之一是,在 Windows 上使用 UTF-16 很容易,而使用 UTF-8 很难,但在 Linux 上反之亦然。我通过编写这样的函数来处理它:
FILE* fopen_utf8(const char* filename, const char* mode)
{
#ifdef _WIN32
std::wstring wfilename = ConvertUtf8ToUtf16(filename);
std::wstring wmode = ConvertUtf8ToUtf16(mode);
return _wfopen(wfilename.c_str(), wmode.c_str());
#else
return fopen(filename, mode);
#endif
}