关于VS2005下中文输出的问题

关于VS2005下中文输出的问题
2010年08月29日 星期日 23:47

【不设置全局本地化环境时】

在VS2005下用C/C++写程序,如果程序没有调用setlocale函数设置本地化环境,则cout,printf都能正常的输出中文。然而,所有涉及宽字节串和多字节串的中文相互转换的功能都将以失败告终,比如:调用wcstombs函数或mbstowcs,或者使用了间接调用这两个函数的功能时,也会出现问题,比如:printf("%ls", L"中文"); 或者 wprintf(L"%s", L"中文"); 都无法正确的输出中文。因为wcstombs和mbstowcs这两个函数,如果指定的串中涉及中文时,必须正确的指示中文字符集它们才能做正确的转换(这也是宽字节串串到多字节串的基本要求,想想,都不知道字符集,怎么转嘛)。

特别说一下wprintf(L"%s", L"中文");,这个为什么也不能成功呢,这个好像没有涉及到宽字节到多字节的转换啊? 这个和Windows的控制台有关。因为printf和wprintf的程序一般都是Windows控制台程序,而Windows的控制台都只支持多字节字符集输出的,因此wprintf的所有输出最终都要通过wcstombs转换成多字节串。

基于同上理由,cout输出中文会成功,而wcout则将失败:

请看下面的代码(省略了#include语句):

void main(void)

{

     printf("printf(char *): %s\n", "中文"); //(1)

     printf("printf(wchar_t *): %ls\n", L"中文"); //(2)

     wprintf(L"wprintf(char *): %hs\n", "中文"); //(3)

     wprintf(L"wprintf(wchar_t *): %ls\n", L"中文");//(4)

     std::cout << "cout(char *): " << "中文" << std::endl; //(5)

     std::wcout << "wcout(wchar *): " << L"中文" << std::endl; //(6)
}

输出:

printf(char *): 中文

printf(wchar_t *): wprintf(char *): 中文

wprintf(wchar_t *): ??

cout(char *): 中文

wcout(wchar *):

从输出结果可以看出,代码(1)(3)可以正常输出中文,而代码(2)(4)则不行。

(1)(2)(4)(5)(6)的输出结果符合我们之前提出来的结论。

而对于(3)为什么能成功输出呢,或许初看可能不会理解--因为给人的感觉,三应该是先输出成宽字节字符串,然后再转换成多字节字符串输出的。其实并且如此--对于wprintf,对于前面的format串(即L"wprintf(char *): %hs\n"这个串)是需要先进行 wcstombs转换的,但这个串本身没有包含中文,所以可以正常输。而在输出到%hs则,wprintf就知道,后面要输出的字符串是一个多字节串,所以它不用进行wcstombs的转换,而是直接输出。换句话说,如果我们使用这样的输出语句:

     wprintf(L"wprintf(char *): 中文");

他就不会正确的输出中文。不信?可以自己试试:)

【设置本地化环境时】

好,即然要求设置本地化环境,那我们就设置吧。在程序开始前调用:setlocale(LC_ALL, ""); ,这样代码就变成了:

void main(void)

{

     setlocale(LC_ALL, "");

     printf("printf(char *): %s\n", "中文");   //(1)

     printf("printf(wchar_t *): %ls\n", L"中文"); //(2)

     wprintf(L"wprintf(char *): %hs\n", "中文"); //(3)

     wprintf(L"wprintf(wchar_t *): %ls\n", L"中文"); //(4)

     std::cout << "cout(char *): " << "中文" << std::endl; //(5)

     std::wcout << "wcout(wchar *): " << L"中文" << std::endl; //(6)

}

按照我们想像的,这样所有的输出都应该正常了吧,字符集都已经正确设置,任何转换都应该没有问题了,那还能咋的?

别急,先看看输出结果:

printf(char *): 中文

printf(wchar_t *): 中文

wprintf(char *): 中文

wprintf(wchar_t *): 中文

cout(char *): wcout(wchar *):

结果可以看出(1)(2)(3) (4)成功,(5)(6)失败。也就是说,所有的C函数都成功了,但是使用C++的iostream的方式都失败了。更奇怪的是cout和wcout全部都失败。这是怎么回事呢?

首先,这个问题在VS2005上才有,VC6.0下是不正在的(其它版本的VC没有测试过)。也就是说,在VC6.0下,(1)(2)(3)(4)(5)(6)全部都能成功的输出中文。

这是VS2005的BUG么?这个我不好下定论,但是通过分析相关的源代码,我们可以找到原因。

先看看iostream的处理方式:

(1) 首先,无论是wcout还是cout,都是一个字符一个字符输出的(使用内部的putch函数);

(2) wcout在输出时,会将先宽字节串转换成多字节串,然后一个字节一个字节的输出单个字符(使用内部的putch函数);

这个过程看上去是没什么问题,因为 Windows的控制台应该会将两个字节的中文字符自动显示成一个汉字,所以VC6下能成功,VS2005下没有调用setlocale函数时,语句 (5)也能成功。但为什么调用setlocale函数后,语句(5)也不能成功了呢?

分析VS2005的源代码,你会发现,它内部的putch并不是简单的将输出的字符送到控制台,而是先判断一下当前的本地化设置,如果本地化设置不是"C"(即未调用setlocale时的默认本地化设置),就要先将输出的字符转换成宽字符,然后再输出(很BT吧)。而我们知道,单个英文字符转换是没有问题的,但是单个字节的汉字字符则将无法成功(汉字需要2个字节的多字节字符才能转换成一个宽字节字符),所以这个转换就会失败,所以iostream就无法成功输出汉字。

也就是说,只要调用了 setlocale(LC_ALL, "")设置了本地化环境,无论是wcout和是cout都将无法正确的输出汉字;

【发现矛盾】

上面的结论告诉我们一个不可调和的矛盾:

(1) 未调用setlocale时,所有涉及多字节字节与宽字节字节的汉字输出都将失败;

(2) 调用setlocale后,cout和wcout将失败;

其实,中间还有另一个矛盾:

(1) wcout在未调用setlocale时,由于无法将宽字节字符转换成多字节字符,所以无法正确的输出汉字;

(2) 而wcout在调用setlocale后,由于putch的特性,也不能正确的输出汉字;

--也就是说,wcout似乎永远无法输出汉字。

【化解矛盾】

如何化解矛盾呢?

对于前一个矛盾,我还想不出什么办法,只能是看系统的要求了:

·    如果系统(直接的或间接的)需要用到mbstowcs或wcstombs进行宽字节字符与多字节字符之间的转换,则因为必须调用setlocale,所以建议不要使用iostream的方式向屏幕输出中文;

·    如果系统要求使用cout和wcout输出中文,则一定不要使用setlocale 来设置本地化环境。

而对于后一个矛盾,如果系统要求使用wcout又需要输出中文,那应该怎么办呢?答案是使用C++的本地化环境设置方式,而不是使用C的全局全地化环境设置。比如:在wcout输出中文前加一条对wcout单独设置本地化环境的语句:

     std::wcout.imbue(std::locale(""));

     std::wcout << "wcout(wchar *): " << L"中文" << std::endl; //(6)

这样一下,语(6)就能正确输出汉字了。但新的问题又来了,试试下面的代码的输出结果:

     std::wcout.imbue(std::locale(""));

     std::wcout << 123456 << L"个汉字" << std::endl; //(6)

猜猜输出结果是什么?是123456个汉字么?否。真正的输出结果是:

123,456个汉字

字符3后面多了分隔个千分位的逗号。怎么会这样呢?因为我们设置了所有的本地化环境,不仅字符集本地化了,数字格式的输出也本地化了,而Windows的默认设置里,中文的数字格式就是这样的--你可以看看Windows控制面板中的区域语言选项中的设置。

如果不希望这样的输出结果,可以对 wcout只设置字符集的本地化,而不要设置数字格式的本地化,如将上面的代码改成:

     std::wcout.imbue(std::locale("", LC_CTYPE));

     std::wcout << 123456 << L"个汉字" << std::endl; //(6)

则输出就变成我们期望的:123456个汉字了。

你可能感兴趣的:(关于VS2005下中文输出的问题)