關於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個漢字了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章