寬字符

 

Unicode或者寬字符都沒有改變char數據型態在C中的含義。char繼續表示1個字節的儲存空間,sizeof (char)繼續返回1。理論上,C中1個字節可比8位長,但對我們大多數人來說,1個字節(也就是1個char)是8位寬。

C中的寬字符基於wchar_t數據型態,它在幾個表頭文件包括WCHAR.H中都有定義,像這樣:

typedef unsigned short wchar_t ;
因此,wchar_t數據型態與無符號短整數型態相同,都是16位寬。

要定義包含一個寬字符的變量,可使用下面的語句:

wchar_t c = ’A’ ;
變量c是一個雙字節值0x0041,是Unicode表示的字母A。(然而,因爲Intel微處理器從最小的字節開始儲存多字節數值,該字節實際上是以0x41、0x00的順序保存在內存中。如果檢查Unicode文字的計算機儲存應注意這一點。)

您還可定義指向寬字符串的指針:

wchar_t * p = L"Hello!" ;
注意緊接在第一個引號前面的大寫字母L(代表「long」)。這將告訴編譯器該字符串按寬字符保存-即每個字符佔用2個字節。通常,指針變量p要佔用4個字節,而字符串變量需要14個字節-每個字符需要2個字節,末尾的0還需要2個字節。

同樣,您還可以用下面的語句定義寬字符數組:

static wchar_t a[] = L"Hello!" ;
該字符串也需要14個字節的儲存空間,sizeof (a) 將返回14。索引數組a可得到單獨的字符。a[1] 的值是寬字符「e」,或者0x0065。

雖然看上去更像一個印刷符號,但第一個引號前面的L非常重要,並且在兩個符號之間必須沒有空格。只有帶有L,編譯器才知道您需要將字符串存爲每個字符2字節。稍後,當我們看到使用寬字符串而不是變量定義時,您還會遇到第一個引號前面的L。幸運的是,如果忘記了包含L,C編譯器通常會給提出警告或錯誤信息。

您還可在單個字符文字前面使用L前綴,來表示它們應解釋爲寬字符。如下所示:

wchar_t c = L’A’ ;
但通常這是不必要的,C編譯器會對該字符進行擴充,使它成爲寬字符。

寬字符鏈接庫函數


我們都知道如何獲得字符串的長度。例如,如果我們已經像下面這樣定義了一個字符串指針:

char * pc = "Hello!" ;
我們可以呼叫

iLength = strlen (pc) ;
這時變量iLength將等於6,也就是字符串中的字符數。

太好了!現在讓我們試着定義一個指向寬字符的指針:

wchar_t * pw = L"Hello!" ;
再次呼叫strlen :

iLength = strlen (pw) ;
現在麻煩來了。首先,C編譯器會顯示一條警告消息,可能是這樣的內容:

’’ : incompatible types - from ’unsigned short *’ to ’const char *’

這條消息的意思是:聲明strlen函數時,該函數應接收char類型的指標,但它現在卻接收了一個unsigned short類型的指標。您仍然可編譯並執行該程序,但您會發現iLength等於1。爲什麼?

字符串「Hello!」中的6個字符佔用16位:

0x0048 0x0065 0x006C 0x006C 0x006F 0x0021Intel處理器在內存中將其存爲:

48 00 65 00 6C 00 6C 00 6F 00 21 00假定strlen函數正試圖得到一個字符串的長度,並把第1個字節作爲字符開始計數,但接着假定如果下一個字節是0,則表示字符串結束。

這個小練習清楚地說明了C語言本身和執行時期鏈接庫函數之間的區別。編譯器將字符串L"Hello!" 解釋爲一組16位短整數型態數據,並將其保存在wchar_t數組中。編譯器還處理數組索引和sizeof操作符,因此這些都能正常工作,但在連結時才添加執行時期鏈接庫函數,例如strlen。這些函數認爲字符串由單字節字符組成。遇到寬字符串時,函數就不像我們所希望那樣執行了。

您可能要說:「噢,太麻煩了!」現在每個C語言鏈接庫函數都必須重寫以接受寬字符。但事實上並不是每個C語言鏈接庫函數都需要重寫,只是那些有字符串參數的函數才需要重寫,而且也不用由您來完成。它們已經重寫完了。

strlen函數的寬字符版是wcslen(wide-character string length:寬字符串長度),並且在STRING.H(其中也說明了strlen)和WCHAR.H中均有說明。strlen函數說明如下:

size_t __cdecl strlen (const char *) ; 而wcslen函數則說明如下:

size_t __cdecl wcslen (const wchar_t *) ; 這時我們知道,要得到寬字符串的長度可以呼叫

iLength = wcslen (pw) ; 函數將返回字符串中的字符數6。請記住,改成寬字節後,字符串的字符長度不改變,只是位組長度改變了。

您熟悉的所有帶有字符串參數的C執行時期鏈接庫函數都有寬字符版。例如,wprintf是printf的寬字符版。這些函數在WCHAR.H和含有標準函數說明的表頭文件中說明。

維護單一原始碼


當然,使用Unicode也有缺點。第一點也是最主要的一點是,程序中的每個字符串都將佔用兩倍的儲存空間。此外,您將發現寬字符執行時期鏈接庫中的函數比常規的函數大。出於這個原因,您也許想建立兩個版本的程序-一個處理ASCII字符串,另一個處理Unicode字符串。最好的解決辦法是維護既能按ASCII編譯又能按Unicode編譯的單一原始碼文件。

雖然只是一小段程序,但由於執行時期鏈接庫函數有不同的名稱,您也要定義不同的字符,這將在處理前面有L的字符串文字時遇到麻煩。

一個辦法是使用Microsoft Visual C++包含的TCHAR.H表頭文件。該表頭文件不是ANSI C標準的一部分,因此那裏定義的每個函數和宏定義的前面都有一條底線。TCHAR.H爲需要字符串參數的標準執行時期鏈接庫函數提供了一系列的替代名稱(例如,_tprintf和_tcslen)。有時這些名稱也稱爲「通用」函數名稱,因爲它們既可以指向函數的Unicode版也可以指向非Unicode版。

如果定義了名爲_UNICODE的標識符,並且程序中包含了TCHAR.H表頭文件,那麼_tcslen就定義爲wcslen:

#define _tcslen wcslen 如果沒有定義UNICODE,則_tcslen定義爲strlen:

#define _tcslen strlen 等等。TCHAR.H還用一個新的數據型態TCHAR來解決兩種字符數據型態的問題。如果定義了_UNICODE標識符,那麼TCHAR就是wchar_t:

typedef wchar_t TCHAR ; 否則,TCHAR就是Char:

typedef char TCHAR ; 現在開始討論字符串文字中的L問題。如果定義了_UNICODE標識符,那麼一個稱作__T的宏就定義如下:

#define __T(x) L##x 這是相當晦澀的語法,但合乎ANSI C標準的前置處理器規範。那一對井字號稱爲「粘貼符號(token paste)」,它將字母L添加到宏參數上。因此,如果宏參數是"Hello!",則L##x就是L"Hello!"。

如果沒有定義_UNICODE標識符,則__T宏只簡單地定義如下:

#define __T(x) x 此外,還有兩個宏與__T定義相同:

#define _T(x)__T(x)
#define _TEXT(x)__T(x) 在Win32 console程序中使用哪個宏,取決於您喜歡簡潔還是詳細。基本地,必須按下述方法在_T或_TEXT宏內定義字符串文字:

_TEXT ("Hello!") 這樣做的話,如果定義了_UNICODE,那麼該串將解釋爲寬字符的組合,否則解釋爲8位的字符字符串。

寬字符和 Windows


Windows NT從底層支援Unicode。這意味着Windows NT內部使用由16位字符組成的字符串。因爲世界上其它許多地方還不使用16位字符串,所以Windows NT必須經常將字符串在操作系統內轉換。Windows NT可執行爲ASCII、Unicode或者ASCII和Unicode混合編寫的程序。即,Windows NT支持不同的API函數呼叫,這些函數接受8位或16位的字符串(我們將馬上看到這是如何動作的。)

相對於Windows NT,Windows 98對Unicode的支持要少得多。只有很少的Windows 98函數呼叫支持寬字符串(這些函數列在《Microsoft Knowledge Base article Q125671》中;它們包括MessageBox)。如果要發行的程序中只有一個.EXE文件要求在Windows NT和Windows 98下都能執行,那麼就不應該使用Unicode,否則就不能在Windows 98下執行;尤其程序不能呼叫Unicode版的Windows函數。這樣,將來發行Unicode版的程序時會處於更有利的位置,您應試着編寫既爲ASCII又爲Unicode編譯的原始碼。

應用:

// 編譯環境 VC++6.0
#i nclude <stdio.h>
#i nclude <wchar.h>
#i nclude <stdlib.h>
#i nclude <ctype.h>

void     AnalyzeStr(wchar_t *str){
         wchar_t meter[] = L",。;:?!";
         char *name[] = { "逗號","句號","分號","冒號","問號","感嘆號"};
         int count[6] = {0};
         size_t     length,i;
         wchar_t *p;
    
         length = wcslen((wchar_t*)str);
         for(i = 0; i < length; i ++){
             if(iswpunct(str[i])){
                 p = wcschr((wchar_t*)meter,str[i]);
                 if(p==NULL){
                     printf("標點:%wc,不在記錄裏\n",str[i]);
                     continue;
                 }
                 count[p-meter] ++;
             }
         }
         for(i = 0; i < 6; i ++){
             if(count[i])
                 printf("%s出現了%d次\n",name[i],count[i]);
         }
}

int main(){
     wchar_t test[] = L"中國。臺灣,一定?回國;不是嗎?";

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