我們熟悉的ASCII碼全稱是美國國家信息交換標准碼,它起源於20世紀50年代末,並於1967年最終定型。ASCIIS碼使用7位(bit)的寬度,有26個小寫字母,26個大寫字母,10個數字,32個符號,33個控制碼,一個空格碼,共128個代碼。 ASCII的使用相當普及,是一種非常可靠的標准。但是,ASCII是一個真正的美國標准,它甚至滿足不了其他英語國家的需求,例如,ASCII碼並沒有英鎊符號。 我們知道,一些語言文字系統(例如中國的漢字)的字符集有非常多的符號,但一個字節最多只能表示256個符號,這是遠遠不夠的。為了支持這些文字系統,雙字節字符集(doube-byte character set, DBCS)應運而生。在雙字節字符集中,一個字符由1個或2個字節組成。對程序員來說,和雙字節字符集打交道就如同一場噩夢,因為程序員需要判斷每個字節是否雙字節的前導字節。 與DBCS的混亂不同,Unicode統一使用16位進行編碼,即UTF-16編碼。UTF的全稱是Unicode Transformation Format。UTF-16將每個字符編碼2個字節(16位)。這樣一來,應用程序很容易遍歷字符串長度。 一、char數據類型 我們知道,C語言用char數據類型來表示一個8位ANSI字符。當在代碼中聲明一個字符串時,C編譯器會把字符串中的字符轉換成由8位char數據類型組成的數組。例如, [cpp] char c = 'A'; char szBuffer[100] = "A String"; 可以定義一個指向字符串的指針: [cpp] char *p; 由於windows是一個32位的系統,指針變量p需要4個字節的存儲空間。也可以定義並初始化一個指向字符串的指針: [cpp] char *p = "Hello!"; 變量p和之前一樣,也只是需要4個字節的空間。字符串存儲在靜態內存中並使用7個字節來存儲——其中6個字節存儲字符串,另一個字節存儲結束的'\0'。 二、wchat_t類型 Microsoft的C/C++編譯器定義了一個內建的數據類型wchat_t,表示一個16位的Unicode(UTF—16)字符。 聲明Unicode字符和字符串的方法如下: [cpp] wchar_t c = L'A'; wchar_t szBuffer[100] = L"A String"; 字符串前面的大寫字母L通知編譯器該字符串應當編譯為一個Unicode字符串。當編譯器將此字符串放入程序的數據段時,會使用UTF-16來編碼每個字符。 為了與C語言有一些區分,Windows開發團隊希望定義自己的數據類型。於是,他們定義了以下數據類型: [cpp] typedef char CHAR; // 一個8位的字符 typedef wchar_t WCHAR; // 一個16位的字符 除此之外,Windows還定義了一系列為我們提供方便的數據類型,可以用它們來處理字符指針和字符串指針: [cpp] // 指向8位字符(串)的指針 typedef CHAR *PCHAR; typedef CHAR *PSTR; typedef CONST CHAR *PCSTR; // 指向16位的字符(串)指針 typedef WCHAR *PWCHAR; typedef WCHAR *PWSTR; typedef CONST WCHAR *PCWSTR; 三、維護一個源代碼 在寫代碼的時候,可以使用ANSI 或Unicode字符/字符串。為使其能通過編譯,windows定義了以下的類型的宏: [cpp] #ifdef UNICODE typedef WCHAR TCHAR, *PTCHAR, PTSTR; typedef CONST WCHAR *PCTSTR; #define __TEXT(quote) L##quote #else typedef CHAR TCHAR, *PTCHAR, PTSTR; typedef CONST CHAR *PCTSTR; #define __TEXT(quote) quote #endif #define TEXT(quote) __TEXT(quote) 利用這些類型和宏,無論使用ANSI還是Unicode,都可以通過編譯。 [cpp] // 若定義了UNICODE,則作用16位的字符,否則使用8位的字符 TCHAR C = TEXT('A'); // 若定義了UNICODE,則作用16位的字符串,否則使用8位的字符串 TCHAR szBuffer[100] = TEXT("A String"); 四、Windows中的Unicode函數和ANSI函數 如果一個Windows函數的參數列表中有字符串,則該函數通常有兩個版本。例如MessageBox函數,它有兩個入口點,一個名為MessageBoxA接受ANSI字符串,一個名為MessageBoxW接受Unicode字符串。 MessageBoxA的定義如下: [cpp] int WINAPI MessageBoxA (HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); MessageBoxW的定義如下: [cpp] int WINAPI MessageBoxW (HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType); 注意第二第三個參數分別指向8位和16位的字符串。 在編寫代碼時,我們只需使用MessageBox,根據是否已定義UNICODE標識符,會自動選擇MessageBoxA函數還是MessageBoxW函數。 [cpp] #ifdef UNICODE #define MessageBox MessageBoxW #else #define MessageBox MessageBoxA 五、C運行庫中的Unicode函數和ANSI函數 在C運行庫中,strlen是一個返回ANSI字符串長度的函數,與之對應的是wcslen函數,它返回的是Unicode字符串的長度。 為方便使用,定義了以下的宏: [cpp] #ifdef _UNICODE #define _tcslen wcslen #else #define _tcslen strlen #endif 這樣,只需要代碼中使用_tcslen,即可以獲得字符串的長度。 六、推薦的字符和字符串處理方式 1. 將文本字符串想象為字符的數組,而不是char或者字節的數組 2. 使用通用的數據類型(如TCHAR/PTSTR)來表示文本字符和字符串 3. 用明確的數據類型(如BYTE或PBYTE)表未字節,字節指針和數據緩沖區 4. 用TEXT或_T宏來表示字面常字符和字符串,但為了保持一致,請避免混用 5. UNICODE和_UNICODE符號要麼同時指定,要不都不指定 6. 避免使用printf系列函數,尤其不要使用%s和%S來進行ANSI與Unicode字符串之間的轉換,正確的做法是使用MultiByteToWideChar和WideCharToMultiByte函數 7. 修改有關字符串的計算。例如,函數經常希望傳給它的是緩沖區大小的字符數,而不是字節數,這時應使用_countof(szBuffer),而不是sizeof(szBuffer)。如果要為一個字符串分配內存塊,那麼請記住內存是以字節來分配的。這意味著需使用malloc(nCharacters * sizeof(TCHAR)),而不是調用malloc(nCharacters)。