寬字元與控制台輸出

2021-07-29 19:00:37 字數 3068 閱讀 6450

在我剛開始學c/c++的時候,字元型別使用的都是char。接觸win32程式設計之後,養成了使用wchar_t的習慣,於是再寫控制台程式的時候自然就使用wchar_t了。然而在控制台程式中使用寬字元會導致各種奇怪的問題,這些問題主要是在輸出上。下面分享一下我在這方面的心得。

首先來看一下這段**:

#include

int main()

wprintf用於輸出寬字元型別的字串,看上去似乎沒有錯誤。但這段**的輸出卻是三個問號。這是使用wprintf時最典型的問題。解決方法是加入對_wsetlocale的呼叫:

#include

#include

int main()

_wsetlocale是setlocale的寬字元版本,這兩個函式的區別只在於返回值以及第二個引數使用的是否寬字元字串,執行效果都是一樣的。

要解釋這段**,首先要從控制台本身說起。凡是涉及到字元處理的地方都要用到字符集,而控制台是乙個字元環境,因此控制台也需要使用字符集,它所使用的字符集叫做**頁,每乙個**頁大致上對應一種自然語言,它定義了這種語言的字元如何與二進位制**相關聯。例如,表示英語的**頁是437,表示簡體中文的**頁是936。乙個控制台視窗只能有乙個活動**頁,所以不同語言的字元不能同時出現在乙個控制台視窗中,除非這個字元是兩者共有的,且有相同的二進位制**。可以通過chcp命令來改變當前控制台視窗所使用的**頁。

**頁實際上是一種多位元組字符集,所以控制台本質上不支援unicode。因此,如果直接向控制台輸出寬字元,將不會得到正確的顯示。必須先將寬字元轉換成多位元組字元,再進行輸出。而wprintf函式在內部也的確是進行了這種轉換,可以嘗試一下在wprintf函式內單步執行,會看到執行過程最終到達wcstombs_s。

問題出現在轉換的過程上。轉換函式必須知道將寬字元的二進位制**轉換成哪種**頁字元的二進位制**,如果選擇的**頁與控制台的活動**頁不相符,那麼同樣也不會正確顯示。上面的第一段**正是由於沒有選擇合適的**頁,導致輸出錯誤。而在第二段**中,通過將區域設定為中國,告訴轉換函式將寬字元轉換成936**頁的多位元組字元,這與控制台的活動**頁一致,所以就可以正確輸出了。

這裡簡單介紹一下_wsetlocale函式。該函式設定c執行庫使用的區域文化。區域文化影響到數字、貨幣以及時間等數值的顯示格式,當然還有**頁。第乙個引數指示使用區域文化的哪個方面,取值可以是lc_collate,lc_ctype,lc_monetary,lc_numeric,lc_time以及lc_all。例如,如果使用lc_numeric,則c執行庫輸出數字的時候將使用指定區域文化的數字顯示風格;如果使用lc_ctype,則只影響轉換函式所選擇的**頁。

第二個引數通過字串指定區域文化。該字串有乙個固定的格式,詳細情況可以參見msdn文件。但一般情況下我們只需使用國家或地區的縮寫即可,例如「chs」。如果使用空字串「」,則表示根據當期作業系統的區域設定選擇相應的**頁。所以如果作業系統選擇的區域是「中文(中國)」,則也可以通過_wsetlocale(lc_all, 「」)來設定正確的**頁。

c執行庫預設使用乙個名為「c」的區域文化,這是語言無關的,具有國際通用性,與其關聯的**頁僅包含了ascii中定義的字元。在程式啟動的時候c執行庫會以setlocale(lc_all, 「c」)的方式呼叫setlocale,所以預設情況下wprintf不能正確輸出含有中文的寬字元字串。

c語言下對寬字元的輸出處理就這樣了。接下來看看c++對寬字元的輸出處理。_wsetlocale只對c執行庫有效,對cout和wcout是沒有影響的。對於cout和wcout,應該使用其成員方法imbue:

std::wcout.imbue(std::locale("chs", std::locale::all));

locale物件構造方法的兩個引數與_wsetlocale函式引數的意義是一樣的,只是位置調轉了。

與wprintf一樣,wcout在輸出寬字元字串的時候,也是先將其轉換成多位元組字元字串。不同的是,遇到**頁上不支援的字元的時候,wprint輸出乙個問號,而wcout無任何輸出,同時將badbit和failbit置位,後續的輸出全部都無效。個人認為wcout的處理方式欠妥,因為並不是所有場合都適合這樣處理,還是wprintf的處理方式比較通用。

基於我自己的經驗,個人認為對於控制台程式最好還是使用多位元組字符集,而不要使用unicode字符集。即不要定義_unicode和unicode標記。一旦使用了unicode字符集,在輸出上可能會出現很多莫名其妙且麻煩的問題。

最後對在網上看到的將char*字串轉換成wchar_t*字串的方法發表一下看法。該方法的**如下:

#include

#include

using namespace std;

int main()

具體思路是:將char*型別的字串輸出到wostringstream物件中,再通過該物件的str方法獲取轉換後的字串。這種方法作出了假設:wostringstream物件會自動將char*字串轉換成wchar_t*型別字串。注意在這段**中,沒有呼叫wcout.imbu方法設定區域文化,但仍然能夠正確輸出中文。

編譯、執行這段**都沒有問題,看上去似乎是正確的。但是如果試圖獲取轉換後的字串的長度就出問題了:

#include

#include

using namespace std;

int main()

這段程式將輸出6,而不是3。除了長度之外,使用at方法獲取到的字元也不是「」中的乙個。實際上,對該字串進行操作的結果幾乎都是不正確的。

為什麼會出現這種情況呢?可以通過觀察一下outstrstream物件內部的資料來尋找答案。下圖是執行outstrstream << ""之後的記憶體資料:

紅色框內的便是outstrstream物件內的資料。再來看看寬字元與多位元組字元的「」字串在記憶體中的實際資料:

#include

#include

using namespace std;

int main()

上面的圖是wchat_t*型別的,下面的圖是char*型別的。通過這幾幅圖,可以看到outstrstream物件內的字串仍然是多位元組字元型別的字串,只不過每個位元組擴充套件成了兩個位元組。這根本不是寬字元型別的字串,所以即使不呼叫wcout.imbue也能正確輸出中文。

就寫到這裡吧。以上內容都是個人見解,如果存在錯誤疏漏請見諒。

C C 寬字元與控制台程式

在我剛開始學 c c 的時候,字元型別使用的都是 char 接觸win32 程式設計之後,養成了使用 wchar t 的習慣,於是再寫控制台程式的時候自然就使用 wchar t 了。然而在控制台程式中使用寬字元會導致各種奇怪的問題,這些問題主要是在輸出上。下面分享一下我在這方面的心得。首先來看一下這...

控制台輸出控制

by jingzhongrong 通過win32api提供的函式,可以對控制台程式的輸出進行控制,例如字型顏色 標題文字,以及各種屬性等等。主要使用到的函式以及宣告如下 handle getstdhandle dword nstdhandle 此函式用於獲取控制台輸出 輸入控制代碼。得到控制代碼之後...

讀取控制台輸出

在新建工程窗體中新增button1 memo1 unit unit1 inte ce uses windows,messages,sysutils,variants,classes,graphics,controls,forms,dialogs,stdctrls,extctrls type tfor...