问题现象(使用VS 2008, UNICODE字符集):
wfstream f; wstring path = L"C:\\1.txt"; f.open( path.c_str(), ios::in);
打开文件后,读取文件内容正常,包括中文字符。
wofstream f; wstring path = L"C:\\1.txt";
wstring strText = L"test测试";
f.open( path.c_str(), ios::out);
f << strText.c_str() << std::endl;
打开文件后,无法写入中文字符。
解决方法:
wofstream f; wstring path = L"C:\\1.txt";
wstring strText = L"test测试";
f.open( path.c_str(), ios::out); if ( f.fail()) return false; locale oldLocale = f.imbue(locale(locale(""), "", LC_CTYPE));
f << strText.c_str() << std::endl; f.imbue(oldLocale); f.close();
打开文件后,写入中文字符正常。
引申内容:
(1)iostream library 参见 http://www.cplusplus.com/reference/iostream/
(2)MBCS与UNICODE字符集 原文链接 http://blog.csdn.net/jsufcz/article/details/3514568
本文并不打算讲解Unicode的编码问题,因为本文主要对以下几个问题提一些见解:
1. MBCS多字节码的原理?
2. MBCS与Unicode的关系?
3. MBCS与Unicode的转换?
4. MBCS与Unicode的打印,乱码解决?
早在Windows采用Unicode统一编码进行语言管理之前,Windows为了能够进行非ANSI标准字符的输出,于是采用两个字节来表示这些语言文字。因为这些双字节文字和ANSI是混和在一起的,为了加以区别,Windows将这些字符的最高位置为1(即这些双字节文字的每个字节都>=127),所以这种表示法可以表示 127x127 约一万多种非ANSI文字 ,其本上可以表示任何一种语言的常用文字了。于是,Windows为每一个区域版本,都制定了分别独立的文字编码,这就是MBCS(多字节码)。
在采用Unicode之后,Windows仍然保留了MBCS技术,只不过它对每一种MBCS与Unicode建立了一种映射关系,当然这是通过Unicode的语言区域码实现的。windows对每个语言区域进行编号,并记录其范围。这样,只要给定这些区域编号,就可以实现任何MBCS与Unicode的转换。
在VS编程环境下,L""表示Unicode字符(请切记:WCHAR即ushort只表示宽字符,而宽字符并不就是unicode,反而Unicode属于宽字符 ),可喜的是,VS编译器直接将L""宏编译成了Unicode编码。我们可以使用%S等进行转换,如
setlocale(LC_ALL,""); //这句很重要,后面会讲
WCHAR swzMsg[] = L"Unicode测试";
char szMsg[32] = {0};
sprintf(szMsg, "%S", swzMsg); //这里%S表示进行MBCS/Unicode转换
反之,可以如下转:
WCHAR swzMsg2[32] = {0};
swprintf(swzMsg, L"%S", szMsg);
我们仔细分析上面字符串的长度和编码:
swzMsg : 55 00 6e 00 69 00 63 00 6f 00 64 00 65 00 4b 6d d5 8b 00 00
szMsg: 55 6e 69 63 6f 64 65 b2 e2 ca d4 00
注意到了没,swzMsg是Unicode编码的,其中文字"测试"部分是 4b 6b d5 8b,即"测" 6b4b,"试"8bd5,可以看出是很接近的一段区域了吧。
而 szMsg其中文部分是 b2 e2 ca d4,即“测" e2b2,"试" d4ca,跟上面分析的一样吧,四个字节都大于127,e2b2 d4ca就是他们的MBCS码,
内码是(d2-127)(b2-127) (d4-127)(ca-127)。
Unicode字符串用wcslen()求长度,MBCS用strlen()求长度 ,原因我想大家都很清楚。
如果一个应用程序使用MBCS多字节编码,我们在中文环境下编译,再拿到韩文操作下去运行,会出现什么情况呢?肯定是乱码!
原因:中文环境下编译的MBCS中文字符被编译成了MBCS内码(小于127x127的连续码),而在韩文系统下,这些码可能对应了一些韩文字符,当然也有可能什么都没有。
其实,现代操作系统版本,如Windows内部已经用Unicode来表示各种文字编码了,它能识别任务它能表示的文字字符,
Unicode字符 <-> 区域码起始值 + MBCS内码
MBCS内码 = (高位MBCS外码-127)<<8 | (低位MBCS外码-127)
操作系统内还记录了区域码CodePage,如中文是.936,它对应了一个Unicode起始值。
任何一个Unicode字符,操作系统都可以根据区域码计算出其MBCS码。
OK,讲到这里,
setlocale(LC_ALL,"");
再次登场了,它表示设置区位码,""串表示使用当前的,如果不使用本语句,
wprintf(L"测试Unicode");
将不能正常显示,
sprintf(szMsg, "%S", swzMsg);
swprintf(swzMsg, L"%S", szMsg);
转换也会出现问题。
关于这点,我感到很奇怪,%S在进行MBCS/Unicode字符串转换 和 wprintf()在输出Unicode字符串的时候,为什么运行时刻库不能自已默认使用
setlocale(LC_ALL, "");
而 L"" 又能使用默认区域码进行MBCS/Unicode转换,我想这应该是标准C时刻库和编译器版本之间的差异造成的吧。因此,
微软的WideCharToMultiByte和MultiByteToWideChar就做得好一些,我想它应该内部给我们调用了setlocale(LC_ALL, "")语句 。
WCHAR swzMsg[] = L"Unicode测试";
char szMBAA[12] = {0};
::WideCharToMultiByte(CP_ACP, 0, swzMsg, -1, szMBAA, sizeof(szMBAA), NULL, NULL);
printf("%s /n", szMBAA);
WCHAR szWWAA[12] = {0};
::MultiByteToWideChar(CP_ACP, 0, szMBAA, -1, szWWAA, sizeof(szWWAA));
(3)ofstream和wofstream与中文输出问题 参见 http://archive.cnblogs.com/a/2174346/
我使用的是VS2008 UNICODE字符集,但是验证了一下该文章中说的,存在几点问题:
1、第二项中不需要将全局locale恢复成原来的设置,并不会影响cout,wcout输出中文,并且printf,wprintf都能正常输出中文。
2、第三项中使用setlocale后,其他都正常,但是wcout不能输出中文,即使是像他说的恢复后,仍然不能输出中文。
关于 C Locale,C++ Locales可以参见 http://stdcxx.apache.org/doc/stdlibug/24-3.html
但是还没有查出为什么用setlocale对于wcout输出中文没有效果(先记着)。