1. 程式人生 > >寬字元和窄字元的一個坑

寬字元和窄字元的一個坑

學習Windows程式設計的時候,遇到字串處理會讓人非常抓狂,當然問題的根本還是自己學藝不精,不過還是得吐槽一下,造成這一局面的原因是規則變化多端而又有點不可捉摸,這不,最近就掉到坑裡面去了。

先看看下面的這段程式碼:

int main(int argn,char* argv[])
{
    char strA[]="ABC 簡體中文";
    wchar_t strW[]=L"ABC 簡體中文";
    printf("%s\n",strA);
    wprintf(L"%s\n",strW);
    return 0;
}

猜猜看,輸出是什麼?在我的電腦上(程式使用VS2010編譯通過,Windows 7作業系統,簡體中文版)執行的結果是這樣的:

image

第一個還好好地,怎麼第二個會出現三個問號?

除錯一下試試看,在return 0前面下斷點,然後檢視記憶體:

image

image

這個是strA在記憶體中的值:41 42 43 20 bc f2 cc e5 d6 d0 ce c4 00

而strW則是:41 00 42 00 43 00 20 00 80 7b 53 4f 2d 4e 87 65 00 00

首先,我們知道char型別佔一個位元組,而wchar_t則是佔兩個位元組,前面的41,42,43,20就是分別’A’,’B’,’C’和’ ‘(空格),這裡表明,寬字符采用的是Little-Endian方式存放兩個位元組的,接下來我們把重點都放在漢字上面。

在strA中,表示漢字“簡體中文”資料為bc f2 cc e5 d6 d0 ce c4,而在strW則是80 7b 53 4f 2d 4e 87 65,差別很大,為什麼是這樣呢?

這其中涉及到了編碼的問題。char型別中出現的漢字,採用的是GB2312的編碼規則,查詢該編碼表,可以發現,“簡”字的編碼為BCF2,“體”為CCE5,“中”為D6D0,而“文”則為CEC4,這就是strA中中文的表示方式,但是在寬字元strW中,採用的編碼則是Unicode編碼,在Windows平臺下Unicode編碼值就是UTF-16編碼值。查詢“簡體中文”四個漢字的編碼,可以發現是7B80 4F53 4E2D 6587,由於計算機的架構為Little-Endian,需要把高低位位元組互換,這也就是寬字元的表示形式。

根據網頁上的說明(參考這裡)C/C++標準只是宣告wchar_t是一個可以表示字符集中的任意一個字元的足夠寬的變數型別。wchar_t可以用任何encoding編碼方式來儲存這個字元,如ANSI、UCS-2或者UCS-4, 甚至是SCU-128,只不過我們通常是用unicode編碼方式。wchar_t是與實現相關的。所以為了可移植性,我們不能假定wchar_t的編碼方式,然後根據編碼方式做一些相關性操作,我們只能理解它為一個足夠寬的字元型別。

最後,我們還能順便發現,wprintf函式在處理文字輸出的時候,並不處理編碼問題,而是直接按多位元組字元順序輸出。

好,現在問題的原因找到了,那麼該如何解決問題呢?

Windows當然不會沒有想到這個問題,在Windows中,提供瞭如下兩個函式:WideCharToMultiByte和MultiByteToWideChar,他們都位於標頭檔案winnls.h中,分別是將寬字元轉化為多位元組和將多位元組轉化為寬字元。下面的例子直接給出了轉化的程式碼,函式的具體使用方法可以翻閱MSDN。

int main(int argn,char* argv[])
{
    wchar_t strW[]=L"ABC 簡體中文";
    char* pW2A;
    int t=0;
    //第一次,確定需要的位元組數
    t=WideCharToMultiByte(CP_ACP,0,strW,-1,NULL,0,NULL,FALSE);
    if(t!=0)
    {
        pW2A=(char*)malloc(t);//分配記憶體,然後執行第二次,注意引數區別
        WideCharToMultiByte(CP_ACP,0,strW,-1,pW2A,t,NULL,FALSE);
        printf("%s\n",pW2A);
        free(pW2A);
    }
    return 0;
}
這回顯示就沒有什麼問題了,長舒一口氣,暫時從坑裡面爬出來了。