Unicode的C/C++編程

來源:互聯網
上載者:User

點擊這裡查看原文

Unicode的最大好處是這裡只有一個字元集,通俗一點講就是說使用Unicode字元編碼的程式可以在任何國語言的編譯環境下編譯通過,而不會被認為是亂碼,也可以使任何語言的編輯環境下正常顯示字元,而不是亂碼。Unicode有缺點嗎?當然有。Unicode字串佔用的記憶體是ASCII字串的兩倍,然而壓縮檔有助於極大地減少檔案所佔的磁碟空間。

對於C編程,在處理有關字元資料操作時,可以用寬字元資料類型來增加對Unicode編程的支援,從而實現多國語言版本的程式。

  char資料類型

 

假定我們都非常熟悉在C程式中使用char資料類型來定義和儲存字元跟字串。但為了便於理解C如何處理寬字元,讓我們先回顧一下可能在Win32程式中出現的標準字元定義。

下面的語句定義並初始化了一個只包含一個字元的變數:

char c = 'A' ; 

變數c需要1個位元組來儲存,並將用十六進位數0x41初始化,這是字母A的ASCII代碼。

您可以像這樣定義一個指向字串的指標:

char * p ; 

因為Windows是一個32位作業系統,所以指標變數p需要用4個位元組儲存。您還可初始化一個指向字串的指標:

char * p = "Hello!" ; 

像前面一樣,變數p也需要用4個位元組儲存。該字串儲存在靜態記憶體中並佔用7個位元組——6個位元組儲存字串,另1個位元組儲存終止符號0。

您還可以像這樣定義字元數組:

char a[10] ; 

在這種情況下,編譯器為該數組保留了10個位元組的儲存空間。運算式sizeof(a)將返回10。如果數組是整體變數(即在所有函數外定義),您可使用像下面的語句來初始化一個字元數組:

char a[] = "Hello!" ; 

如果您將該數組定義為一個函數的地區變數,則必須將它定義為一個static變數,如下:

static char a[] = "Hello!" ; 

無論哪種情況,字串都儲存在靜態程式記憶體中,並在末尾添加0,這樣就需要7個位元組的儲存空間。

 

寬字元 

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編譯器會顯示一條警告訊息,可能是這樣的內容:

'function' : incompatible types - from 'unsigned short *' to 'const char *'

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

字串“Hello!”中的6個字元佔用16位:

0x0048 0x0065 0x006C 0x006C 0x006F 0x0021 

Intel處理器在記憶體中將其存為:

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編譯的原始碼。這就是本書中所有程式的編寫方式。

 

Windows表標頭檔類型 

正如您在 第一章 所看到的那樣,一個Windows程式包括表標頭檔WINDOWS.H。該檔案包括許多其它表標頭檔,包括WINDEF.H,該檔案中有許多在Windows中使用的基本型態定義,而且它本身也包括WINNT.H。WINNT.H處理基本的Unicode支援。

WINNT.H的前麵包含C的表標頭檔CTYPE.H,這是C的眾多表標頭檔之一,包括wchar_t的定義。WINNT.H定義了新的資料類型,稱作CHAR和WCHAR:

typedef char CHAR ; typedef wchar_t WCHAR ; // wc 

當您需要定義8位字元或者16位字元時,推薦您在Windows程式中使用的資料類型是CHAR和WCHAR。WCHAR定義後面的注釋是匈牙利標記法的建議:一個基於WCHAR資料類型的變數可在前面附加上字母wc以說明一個寬字元。

WINNT.H表標頭檔進而定義了可用做8位字串指標的六種資料類型和四個可用做const 8位字串指標的資料類型。這裡精選了表標頭檔中一些實用的說明資料類型語句:

typedef CHAR * PCHAR, * LPCH,* PCH, * NPSTR,* LPSTR,* PSTR ; typedef CONST CHAR * LPCCH,* PCCH, * LPCSTR, * PCSTR ; 

首碼N和L表示“near”和“long”,指的是16位Windows中兩種大小不同的指標。在Win32中near和long指標沒有區別。

類似地,WINNT.H定義了六種可作為16位字串指標的資料類型和四種可作為const 16位字串指標的資料類型:

typedef WCHAR * PWCHAR, * LPWCH, * PWCH,* NWPSTR,* LPWSTR,* PWSTR ; typedef CONST WCHAR * LPCWCH,* PCWCH, * LPCWSTR,* PCWSTR ; 

至此,我們有了資料類型CHAR(一個8位的char)和WCHAR(一個16位的wchar_t),以及指向CHAR和WCHAR的指標。與TCHAR.H一樣,WINNT.H將TCHAR定義為一般的字元類型。如果定義了標識符UNICODE(沒有底線),則TCHAR和指向TCHAR的指標就分別定義為WCHAR和指向WCHAR的指標;如果沒有定義標識符UNICODE,則TCHAR和指向TCHAR的指標就分別定義為char和指向char的指標:

#ifdef UNICODE typedef WCHAR TCHAR, * PTCHAR ; typedef LPWSTR LPTCH, PTCH, PTSTR, LPTSTR ; typedef LPCWSTR LPCTSTR ; #else typedef char TCHAR, * PTCHAR ; typedef LPSTR LPTCH, PTCH, PTSTR, LPTSTR ; typedef LPCSTR LPCTSTR ; #endif 

如果已經在某個表標頭檔或者其它表標頭檔中定義了TCHAR資料類型,那麼WINNT.H和WCHAR.H表標頭檔都能防止其重複定義。不過,無論何時在程式中使用其它表標頭檔時,都應在所有其它表標頭檔之前包含WINDOWS.H。

WINNT.H表標頭檔還定義了一個宏,該宏將L添加到字串的第一個引號前。如果定義了UNICODE標識符,則一個稱作 __TEXT的宏定義如下:

#define __TEXT(quote) L##quote 

如果沒有定義標識符UNICODE,則像這樣定義__TEXT宏:

#define __TEXT(quote) quote 

此外, TEXT宏可這樣定義:

#define TEXT(quote) __TEXT(quote) 

這與TCHAR.H中定義_TEXT宏的方法一樣,只是不必操心底線。我將在本書中使用這個宏的TEXT版本。

這些定義可使您在同一程式中混合使用ASCII和Unicode字串,或者編寫一個可被ASCII或Unicode編譯的程式。如果您希望明確定義8位字元變數和字串,請使用CHAR、PCHAR(或者其它),以及帶引號的字串。為明確地使用16位字元變數和字串,請使用WCHAR、PWCHAR,並將L添加到引號前面。對於是8位還是16位取決於UNICODE標識符的定義的變數或字串,要使用TCHAR、PTCHAR和TEXT宏。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.