以下问题可能不属于 SO 问题;如果超出范围,请随时告诉我离开。这里的问题基本上是:“我是否正确理解了 C 标准,这是处理问题的正确方法吗?”
我想请求对我对 C(以及 C++ 和 C++0x)中字符处理的理解进行澄清、确认和更正。首先,一个重要的观察:
可移植性和序列化是正交的概念。
便携式的东西是像C这样的东西,unsigned int
, wchar_t
。可序列化的东西是这样的uint32_t
或 UTF-8。 “可移植”意味着您可以重新编译相同的源代码并在每个受支持的平台上获得工作结果,但二进制表示可能完全不同(或者甚至不存在,例如 TCP-over-Carrier Pig)。另一方面,可序列化的东西总是有same代表,例如我可以在 Windows 桌面、手机或牙刷上阅读该 PNG 文件。可移植的东西是内部的,可序列化的东西处理 I/O。可移植的东西是类型安全的,可序列化的东西需要类型双关。 前言>
当谈到 C 中的字符处理时,有两组事情分别与可移植性和序列化相关:
wchar_t
, setlocale()
, mbsrtowcs()
/wcsrtombs()
: C 标准没有提到“编码”;事实上,它与任何文本或编码属性完全无关。它只说“你的入口点是main(int, char**)
;你得到一个类型wchar_t
它可以容纳您系统的所有字符;您可以获得读取输入字符序列并将其转换为可用的 wstring 的函数,反之亦然。
iconv()
UTF-8,16,32:在明确定义的、明确的、固定的编码之间进行转码的函数/库。 iconv 处理的所有编码都得到普遍理解和认可,但有一个例外。
可移植的、与编码无关的 C 世界与其wchar_t
可移植的字符类型和确定性的外部世界是WCHAR-T 和 UTF 之间的 iconv 转换.
那么,我是否应该始终将字符串存储在与编码无关的 wstring 中,通过 CRT 进行接口wcsrtombs()
,并使用iconv()
用于序列化?从概念上讲:
my program
<-- wcstombs --- /==============\ --- iconv(UTF8, WCHAR_T) -->
CRT | wchar_t[] | <Disk>
--- mbstowcs --> \==============/ <-- iconv(WCHAR_T, UTF8) ---
|
+-- iconv(WCHAR_T, UCS-4) --+
|
... <--- (adv. Unicode malarkey) ----- libicu ---+
实际上,这意味着我将为我的程序入口点编写两个样板包装器,例如对于 C++:
// Portable wmain()-wrapper
#include <clocale>
#include <cwchar>
#include <string>
#include <vector>
std::vector<std::wstring> parse(int argc, char * argv[]); // use mbsrtowcs etc
int wmain(const std::vector<std::wstring> args); // user starts here
#if defined(_WIN32) || defined(WIN32)
#include <windows.h>
extern "C" int main()
{
setlocale(LC_CTYPE, "");
int argc;
wchar_t * const * const argv = CommandLineToArgvW(GetCommandLineW(), &argc);
return wmain(std::vector<std::wstring>(argv, argv + argc));
}
#else
extern "C" int main(int argc, char * argv[])
{
setlocale(LC_CTYPE, "");
return wmain(parse(argc, argv));
}
#endif
// Serialization utilities
#include <iconv.h>
typedef std::basic_string<uint16_t> U16String;
typedef std::basic_string<uint32_t> U32String;
U16String toUTF16(std::wstring s);
U32String toUTF32(std::wstring s);
/* ... */
这是仅使用纯标准 C/C++ 以及使用 iconv 的定义良好的 UTF I/O 接口编写惯用的、可移植的、通用的、与编码无关的程序核心的正确方法吗? (请注意,诸如 Unicode 规范化或变音符号替换之类的问题超出了范围;仅当您决定确实想要Unicode(与您可能喜欢的任何其他编码系统相反)是时候处理这些细节了,例如使用像 libicu 这样的专用库。)
Updates
继许多非常好的评论之后,我想添加一些观察结果:
如果您的应用程序明确想要处理 Unicode 文本,您应该将iconv
-转换部分核心及使用uint32_t
/char32_t
-内部使用 UCS-4 字符串。
Windows:虽然使用宽字符串通常没问题,但与控制台(就此而言,任何控制台)的交互似乎受到限制,因为似乎不支持任何合理的多字节控制台编码,并且mbstowcs
本质上是无用的(除了微不足道的扩大)。从 Explorer-drop 接收宽字符串参数GetCommandLineW
+CommandLineToArgvW
可以工作(也许应该有一个单独的 Windows 包装器)。
文件系统:文件系统似乎没有任何编码的概念,只是将任何以空结尾的字符串作为文件名。大多数系统采用字节字符串,但 Windows/NTFS 采用 16 位字符串。在发现哪些文件存在以及处理该数据时(例如char16_t
不构成有效 UTF16 的序列(例如裸代理)是有效的 NTFS 文件名)。标准Cfopen
无法打开所有 NTFS 文件,因为没有可能的转换可以映射到所有可能的 16 位字符串。使用 Windows 特定的_wfopen
可能需要。作为推论,通常没有明确定义的概念来表示给定的文件名包含“多少个字符”,因为首先没有“字符”的概念。买者自负。