博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

关于 ifstream ofstream 的读写问题

Posted on 2011-11-28 17:20  gaozili  阅读(854)  评论(0编辑  收藏  举报

问题现象(使用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输出中文没有效果(先记着)。