我想请教关于C中字符处理的理解是否正确,以及更正和确认。首先,一个重要的观察结果是:
可移植性和串行化是正交概念。
可移植的东西是C、unsigned int、wchar_t等东西。可串行化的东西是uint32_t或UTF-8等东西。“可移植”意味着您可以重新编译相同的源代码,并在每个支持的平台上获得工作结果,但二进制表示可能完全不同(或甚至不存在,例如TCP-over-carrier pigeon)。另一方面,可串行化的东西始终具有相同的表示形式,例如我可以在Windows桌面、手机或牙刷上阅读的PNG文件。可移植的东西是类型安全的,可串行化的东西需要类型游戏。
当涉及C中的字符处理时,有两组与可移植性和串行化相关的事物:
- wchar_t、setlocale()、mbsrtowcs()/wcsrtombs():C标准没有关于“编码”的规定;实际上,它对任何文本或编码属性都是完全不可知的。它只是说“您的入口点是main(int, char**);您获得一个可以容纳您系统中所有字符的类型wchar_t;您获得函数以读取输入字符序列并将其转换为可处理的wstring,反之亦然。 - iconv()和UTF-8,16,32:一个在明确定义、确定、固定编码之间转换的函数/库。iconv处理的所有编码都是被普遍理解和认可的,只有一种例外。
C中可移植、不涉及编码属性的世界与决定性外部世界之间的桥梁是WCHAR-T和UTF之间的iconv转换。
因此,我应该始终在编码不可知的wstring中内部存储我的字符串,通过wcsrtombs()与CRT进行接口交互,并使用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++编写习惯,可移植,通用且与UTF使用iconv定义良好的I / O接口编写程序核心的正确方式?(请注意,像Unicode规范化或变音替换之类的问题超出了范围;只有在您实际上想要Unicode(而不是其他任何编码系统)时,才是处理这些特定问题的时候,例如使用专用库如libicu。)更新:
许多非常好的评论后,我想添加一些观察:
- 如果您的应用程序明确希望处理Unicode文本,则应使iconv转换成为核心组成部分,并在内部使用UCS-4的uint32_t/char32_t-字符串。 - Windows:虽然使用宽字符串通常没问题,但似乎与控制台(任何控制台)的交互受到限制,因为没有支持任何明智的多字节控制台编码,并且mbstowcs基本上无用(除了用于微不足道的加宽)。从Explorer-drop接收来自宽字符串参数,以及GetCommandLineW + CommandLineToArgvW一起工作(也许应该为Windows提供单独的包装器)。 - 文件系统:文件系统似乎没有任何编码概念,只需将任何以空字符结尾的字符串作为文件名即可。大多数系统使用字节字符串,但Windows / NTFS使用16位字符串。当发现存在哪些文件以及处理该数据(例如,不构成有效UTF16的char16_t序列(例如裸替代项)是有效的NTFS文件名)时,您必须小心。标准Cfopen无法打开所有NTFS文件,因为没有可能的转换将映射到所有可能的16位字符串。可能需要使用特定于Windows的_wfopen。作为推论,在一般情况下,“有多少个字符”包含给定文件名没有明确定义的概念,因为首先不存在“字符”的概念。买家自负。
wmain
接受std::vector
作为参数,它不应该是extern "C"
。 (我认为不应该将C++类传递给具有C语言链接的函数。) - Nemo__STDC_ISO_10646__
,则wchar_t
值是 Unicode 代码点。C1x 有__STDC_UTF_16__
和__STDC_UTF_32__
分别用于char16_t
和char32_t
,但 C++0x 似乎没有这两个宏。 - ninjalj