**:
在我剛開始學
c/c++
的時候,字元型別使用的都是
char
。接觸win32
程式設計之後,養成了使用
wchar_t
的習慣,於是再寫控制台程式的時候自然就使用
wchar_t
了。然而在控制台程式中使用寬字元會導致各種奇怪的問題,這些問題主要是在輸出上。下面分享一下我在這方面的心得。
首先來看一下這段**:
#include
intmain()
wprintf
用於輸出寬字元型別的字串,看上去似乎沒有錯誤。但這段**的輸出卻是三個問號。這是使用
wprintf
時最典型的問題。解決方法是加入對
_wsetlocale
的呼叫:
#include
#include
intmain()
_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字符集。這是乙個明顯錯誤的建議,因此將這段內容刪去了。)
最後對在網上看到的將
char*
字串轉換成
wchar_t*
字串的方法發表一下看法。該方法的**如下:
#include
#include
using
namespace std;
intmain()
具體思路是:將
char*
型別的字串輸出到
wostringstream
物件中,再通過該物件的
str方法獲取轉換後的字串。這種方法作出了假設:
wostringstream
物件會自動將
char*
字串轉換成
wchar_t*
型別字串。注意在這段**中,沒有呼叫
wcout.imbu
方法設定區域文化,但仍然能夠正確輸出中文。
編譯、執行這段**都沒有問題,看上去似乎是正確的。但是如果試圖獲取轉換後的字串的長度就出問題了:
#include
#include
using
namespace std;
intmain()
這段程式將輸出
6,而不是
3。除了長度之外,使用
at方法獲取到的字元也不是「」中的乙個。實際上,對該字串進行操作的結果幾乎都是不正確的。
為什麼會出現這種情況呢?可以通過觀察一下
outstrstream
物件內部的資料來尋找答案。下圖是執行
outstrstream <
"之後的記憶體資料:
紅色框內的便是
outstrstream
物件內的資料。再來看看寬字元與多位元組字元的「」字串在記憶體中的實際資料:
上面的圖是
wchat_t*
型別的,下面的圖是
char*
型別的。通過這幾幅圖,可以看到
outstrstream
物件內的字串仍然是多位元組字元型別的字串,只不過每個位元組擴充套件成了兩個位元組。這根本不是寬字元型別的字串,所以即使不呼叫
wcout.imbue
也能正確輸出中文。
寬字元與控制台輸出
在我剛開始學c c 的時候,字元型別使用的都是char。接觸win32程式設計之後,養成了使用wchar t的習慣,於是再寫控制台程式的時候自然就使用wchar t了。然而在控制台程式中使用寬字元會導致各種奇怪的問題,這些問題主要是在輸出上。下面分享一下我在這方面的心得。首先來看一下這段 inclu...
控制台程式裡呼叫控制台程式
現在遇到這麼個問題,我想用控制台裡呼叫控制台程式來實現多文字。但是在控制台裡呼叫控制台程式時,它不是出現新的控制台視窗顯示,而是已有的控制台裡顯示呼叫的程式,system d code 練習 jjplace editor debug editor.exe winexec d code 練習 jjpl...
沒有控制台視窗的控制台程式
include include pragma comment linker,subsystem windows entry maincrtstartup int main int argc,char argv 編譯後執行程式會彈出乙個訊息框,而沒有背後的控制台視窗。再看看下面的 include in...