VC++的Unicode編程

一、什麼是Unicode

  先從ASCII說起,ASCII是用來表示英文字符的一種編碼規範。每個ASCII字符佔用1個字節,因此,ASCII編碼可以表示的最大字符數是255(00H—FFH)。其實,英文字符並沒有那麼多,一般只用前128個(00H—7FH,最高位爲0),其中包括了控制字符、數字、大小寫字母和其它一些符號。而最高位爲1的另128個字符(80H—FFH)被稱爲“擴展ASCII”,一般用來存放英文的製表符、部分音標字符等等的一些其它符號。
  這種字符編碼規則顯然用來處理英文沒有什麼問題。但是面對中文、阿拉伯文等複雜的文字,255個字符顯然不夠用。
於是,各個國家紛紛制定了自己的文字編碼規範,其中中文的文字編碼規範叫做“GB2312—80”,它是和ASCII兼容的一種編碼規範,其實就是利用擴展ASCII沒有真正標準化這一點,把一箇中文字符用兩個擴展ASCII字符來表示,以區分ASCII碼部分。
  但是這個方法有問題,最大的問題就是中文的文字編碼和擴展ASCII碼有重疊。而很多軟件利用擴展ASCII碼的英文製表符來畫表格,這樣的軟件用到中文系統中,這些表格就會被誤認作中文字符,出現亂碼。
  另外,由於各國和各地區都有自己的文字編碼規則,它們互相沖突,這給各國和各地區交換信息帶來了很大的麻煩。
要真正解決這個問題,不能從擴展ASCII的角度入手,而必須有一個全新的編碼系統,這個系統要可以將中文、法文、德文……等等所有的文字統一起來考慮,爲每一個文字都分配一個單獨的編碼。

於是,Unicode誕生了。

  Unicode也是一種字符編碼方法,它佔用兩個字節(0000H—FFFFH),容納65536個字符,這完全可以容納全世界所有語言文字的編碼。
在Unicode裏,所有的字符被一視同仁,漢字不再使用“兩個擴展ASCII”,而是使用“1個Unicode”,也就是說,所有的文字都按一個字符來處理,它們都有一個唯一的Unicode碼。

二、使用Unicode編碼的好處

  使用Unicode編碼可以使您的工程同時支持多種語言,使您的工程國際化。
  另外,Windows NT是使用Unicode進行開發的,整個系統都是基於Unicode的。如果調用一個API函數並給它傳遞一個ANSI(ASCII字符集以及由此派生併兼容的字符集,如:GB2312,通常稱爲ANSI字符集)字符串,那麼系統首先要將字符串轉換成Unicode,然後將Unicode字符串傳遞給操作系統。如果希望函數返回ANSI字符串,系統就會首先將Unicode字符串轉換成ANSI字符串,然後將結果返回給您的應用程序。進行這些字符串的轉換需要佔用系統的時間和內存。如果用Unicode來開發應用程序,就能夠使您的應用程序更加有效地運行。

下面例舉幾個字符的編碼以簡單演示ANSI和Unicode的區別:

字符 A N
ANSI碼 41H 4eH cdbaH
Unicode碼 0041H 004eH 548cH

三、使用C++進行Unicode編程

  對寬字符的支持其實是ANSI C標準的一部分,用以支持多字節表示一個字符。寬字符和Unicode並不完全等同,Unicode只是寬字符的一種編碼方式。

1、寬字符的定義

  在ANSI中,一個字符(char)的長度爲一個字節(Byte)。使用Unicode時,一個字符佔據一個字,C++在wchar.h頭文件中定義了最基本的寬字符類型wchar_t:

typedef unsigned short wchar_t;

從這裏我們可以清楚地看到,所謂的寬字符就是無符號短整數。

2、常量寬字符串

  對C++程序員而言,構造字符串常量是一項經常性的工作。那麼,如何構造寬字符字符串常量呢?很簡單,只要在字符串常量前加上一個大寫的L就可以了,比如:

wchar_t *str1=L" Hello";

這個L非常重要,只有帶上它,編譯器才知道你要將字符串存成一個字符一個字。還要注意,在L和字符串之間不能有空格。

3、寬字符串庫函數

爲了操作寬字符串,C++專門定義了一套函數,比如求寬字符串長度的函數是

size_t __cdel wchlen(const wchar_t*);

  爲什麼要專門定義這些函數呢?最根本的原因是,ANSI下的字符串都是以’/0’來標識字符串尾的(Unicode字符串以“/0/0”結束),許多字符串函數的正確操作均是以此爲基礎進行。而我們知道,在寬字符的情況下,一個字符在內存中要佔據一個字的空間,這就會使操作ANSI字符的字符串函數無法正確操作。以”Hello”字符串爲例,在寬字符下,它的五個字符是:
0x0048 0x0065 0x006c 0x006c 0x006f
在內存中,實際的排列是:

48 00 65 00 6c 00 6c 00 6f 00

  於是,ANSI字符串函數,如strlen,在碰到第一個48後的00時,就會認爲字符串到尾了,用strlen對寬字符串求長度的結果就永遠會是1!

4、用宏實現對ANSI和Unicode通用的編程

  可見,C++有一整套的數據類型和函數實現Unicode編程,也就是說,您完全可以使用C++實現Unicode編程。
如果我們想要我們的程序有兩個版本:ANSI版本和Unicode版本。當然,編寫兩套代碼分別實現ANSI版本和Unicode版本完全是行得通的。但是,針對ANSI字符和Unicode字符維護兩套代碼是非常麻煩的事情。爲了減輕編程的負擔,C++定義了一系列的宏,幫助您實現對ANSI和Unicode的通用編程。
  C++宏實現ANSI和Unicode的通用編程的本質是根據”_UNICODE”(注意,有下劃線)定義與否,這些宏展開爲ANSI或Unicode字符(字符串)。

如下是tchar.h頭文件中部分代碼摘抄:

#ifdef   _UNICODE
typedef wchar_t      TCHAR;
#define __T(x)       L##x
#define _T(x)        __T(x)
#else
#define __T(x)       x
typedef char             TCHAR;
#endif 

  可見,這些宏根據”_UNICODE” 定義與否,分別展開爲ANSI或Unicode字符。 tchar.h頭文件中定義的宏可以分爲兩類:

A、實現字符和常量字符串定義的宏我們只列出兩個最常用的宏:

未定義_UNICODE(ANSI字符) 定義了_UNICODE(Unicode字符)
TCHAR char wchar_t
_T(x) x L##x

注意:
  
“##”是ANSI C標準的預處理語法,它叫做“粘貼符號”,表示將前面的L添加到宏參數上。也就是說,如果我們寫_T(“Hello”),展開後即爲L“Hello”

B、實現字符串函數調用的宏

C++爲字符串函數也定義了一系列宏,同樣,我們只例舉幾個常用的宏:

未定義_UNICODE(ANSI字符) 定義了_UNICODE(Unicode字符)
_tcschr strchr wcschr
_tcscmp strcmp wcscmp
_tcslen strlen wcslen

四、使用Win32 API進行Unicode編程

Win32 API中定義了一些自己的字符數據類型。這些數據類型的定義在winnt.h頭文件中。例如:

typedef char CHAR; 
typedef unsigned short WCHAR;     // wc,    16-bit UNICODE character 
typedef CONST CHAR *LPCSTR, *PCSTR; 

Win32 API在winnt.h頭文件中定義了一些實現字符和常量字符串的宏進行ANSI/Unicode通用編程。同樣,只例舉幾個最常用的:

#ifdef   UNICODE 
typedef WCHAR TCHAR, *PTCHAR;
typedef LPWSTR LPTCH, PTCH;
typedef LPWSTR PTSTR, LPTSTR;
typedef LPCWSTR LPCTSTR;
#define __TEXT(quote) L##quote       // r_winnt
#else    /* UNICODE */                // r_winnt
typedef char TCHAR, *PTCHAR;
typedef LPSTR LPTCH, PTCH;
typedef LPSTR PTSTR, LPTSTR;
typedef LPCSTR LPCTSTR;
#define __TEXT(quote) quote          // r_winnt
#endif /* UNICODE */                 // r_winnt

  從以上頭文件可以看出,winnt.h根據是否定義了UNICODE(沒有下劃線),進行條件編譯。
   Win32 API也定義了一套字符串函數,它們根據是否定義了“UNICODE”分別展開爲ANSI和Unicode字符串函數。如:lstrlen。API的字符串操作函數和C++的操作函數可以實現相同的功能,所以,如果需要的話,建議您儘可能使用C++的字符串函數,沒必要去花太多精力再去學習API的這些東西。
  也許您從來沒有注意到,Win32 API實際上有兩個版本。一個版本接受MBCS字符串,另一個接受Unicode字符串。例如:其實根本沒有SetWindowText()這個API函數,相反,有SetWindowTextA()和SetWindowTextW()。後綴A表明這是MBCS函數,後綴W表示這是Unicode版本的函數。這些API函數的頭文件在winuser.h中聲明,下面例舉winuser.h中的SetWindowText()函數的聲明部分:

#ifdef UNICODE
#define SetWindowText   SetWindowTextW
#else
#define SetWindowText   SetWindowTextA
#endif // !UNICODE

  可見,API函數根據定義UNICODE與否決定指向Unicode版本還是MBCS版本。
  細心的讀者可能已經注意到了UNICODE和_UNICODE的區別,前者沒有下劃線,專門用於Windows頭文件;後者有一個前綴下劃線,專門用於C運行時頭文件。換句話說,也就是在ANSI C++語言裏面根據_UNICODE(有下劃線)定義與否,各宏分別展開爲Unicode或ANSI字符,在Windows裏面根據UNICODE(無下劃線)定義與否,各宏分別展開爲Unicode或ANSI字符。
  在後面我們將會看到,實際使用中我們不加嚴格區分,同時定義_UNICODE和UNICODE,以實現UNICODE版本編程。

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