一個字符可以用1-byte表示,即ANSI編碼;
一個字符也可用2-bytes表示,即Unicode編碼(Unicode其實還包含了更多內容,不止2-bytes)。
Visual C++支持char和wchar_t作為ANSI和Unicode的原始數據類型。
例如
char cResponse; // 'Y' or 'N' char sUsername[64]; // str* functions
以及
wchar_t cResponse; // 'Y' or 'N' wchar_t sUsername[64]; // wcs* functions
它們可以統一寫成
#include<TCHAR.H> // Implicit or explicit include TCHAR cResponse; // 'Y' or 'N' TCHAR sUsername[64]; // _tcs* functions
TCHAR則是根據選擇的字符集決定是翻譯成char還是wchar_t,字符集的設置如下:
所以TCHAR的定義如下:
#ifdef _UNICODE typedef wchar_t TCHAR; #else typedef char TCHAR; #endif
在windows中,一般前綴 T 代表了它可以自適應不同的字符集。
比如:strcpy,strlen,strcat(包括安全後綴_s)代表ANSI版本;
wcscpy,wcslen,wcscat(包括安全後綴_s),代表Unicode版本,這裡WC代表Wide Character;
_tcscpy,_tcslen,_tcscat則視情況而定:
size_t strlen(const char*); //ANSI size_t wcslen(const wchar_t* ); //Unicdoe size_t _tcslen(const TCHAR* ); //ANSI or Unicode
我們知道一個string使用雙引號表示,這種表示說明它是一個ANSI-string,每個字符占1-byte,例如:
"This is ANSI String. Each letter takes 1 byte."
要轉換成Unicdeo-string需要加前綴:L
[__strong__]L"This is Unicode string. Each letter would take 2 bytes, including spaces."
Unicode編碼的字符,每個都占用2-bytes,哪怕是可以用1-byte表示的,比如英文字母,數字,null字符等。所以一個unicode-string占用的字節總是2-bytes的倍數。
結合上面提到的 T 前綴,一種適用於兩種字符集的寫法是這樣的:
"ANSI String"; // ANSI L"Unicode String"; // Unicode _T("Either string, depending on compilation"); // ANSI or Unicode
_T或TEXT是一個宏定義,它與前綴 T 表示的意思一樣,定義如下:
// SIMPLIFIED #ifdef _UNICODE #define _T(c) L##c #define TEXT(c) L##c #else #define _T(c) c #define TEXT(c) c #endif
上面的##叫“token-pasting operator”。在Unicode下,_T("Unicode")被翻譯成 L"Unicode";在ANSI下,_T("Unicode")被翻譯成 “Unicode”。
注意,不能通過_T來轉換一個變量(string or character),下面的操作是不允許的:
char c = 'C'; char str[16] = "CodeProject"; _T(c); _T(str);
如果你是在ANSI(Multi-Byte)下編譯,可以順利通過,_T(c), _T(str)被翻譯成c, str;
但是在Unicode下編譯,就會報錯:
error C2065: 'Lc' : undeclared identifier error C2065: 'Lstr' : undeclared identifier
結合_T的定義不難弄懂。
在windows中,幾乎所有需要傳入string或character的API,都有通用的版本,例如: SetWindowTextA/W,就可以統一寫成:
BOOL SetWindowText(HWND, const TCHAR*);
但我們知道SetWindowText是一個宏,它代表了以下兩種之一:
BOOL SetWindowTextA(HWND, const char*); BOOL SetWindowTextW(HWND, const wchar_t*);
但其實,在內部實現時,不論ANSI還是Unicode都統一通過Unicode方式實現,當你調用 SetWindowTextA 時(傳入ANSI-string),它會先轉化成Unicode-string,再調用 SetWindowTextW實現。真正發揮作用的只有Unicode的版本!
所以在寫代碼時建議是直接調用Unicode版本的api,盡管我們對ANSI版本的string更熟悉。
Note:存在另外一個typedef:WCHAR,它等價於wchar_t。
我們知道strlen定義如下:
size_t strlen(const char*);
它也可以寫成
size_t strlen(LPCSTR);
所以
// Simplified typedef const char* LPCSTR;
它的含義如下
Long Pointer與Pointer意思一樣。
舉一反三,對於Unicode字符,我們有:
size_t wcslen(const wchar_t* szString); // Or WCHAR* size_t wcslen(LPCWSTR szString);
這裡 LPCWSTR代表
typedef const WCHAR* LPCWSTR;
它的含義如下
更進一步,有LPCTSTR
總結:
在編程中有時候會因為選擇的字符集不同,而編譯出錯,如下面的寫法在ANSI下沒事,但在Unicode下就會報錯:
int main() { TCHAR name[] = "Saturn"; int nLen; // Or size_t lLen = strlen(name); }
同樣的問題出現在:
nLen = wcslen("Saturn"); // ERROR: cannot convert parameter 1 from 'const char [7]' to 'const wchar_t *'
遺憾的是,上面的錯誤不能通過強制轉換的方法修改:
nLen = wcslen((const wchar_t*)"Saturn");
上面的寫法會得到錯誤的結果,往往導致越界。原因是“Saturn”占用7個字節
'S'(83) 'a'(97) 't'(116) 'u'(117) 'r'(114) 'n'(110) '\0'(0)但傳給wcslen的時候,對於每個字符分配2-bytes。因此頭兩個字節[83,97]被看作一個字符,value:(97<<8 | 83),是字符'?'.後面的以此類推。
所以如果用Unicode的api,需要提前轉換:
TCHAR name[] = _T("Saturn"); //或者 wcslen(L"Saturn");
在之前的例子中,strlen(name)中的name在Unicode下編譯,每個字符占2-bytes,如果強制轉換成ANSI:
lLen = strlen ((const char*)name);
也會出現問題,‘S'原來表示為[83,0],但在ANSI中第一個字節[83]可以被正確翻譯成'S',但接著第二個字節[0]直接被翻譯為為'\0',結束了整個字符串。所以strlen得到的結果為1。
綜上,C語言風格的強制轉換在這裡是行不通的。
如果需要分配內存,在C++中通過new直接指定字符的個數,不用去管具體分配了多少字節:
LPTSTR pBuffer; // TCHAR* pBuffer = new TCHAR[128]; // Allocates 128 or 256 BYTES, depending on compilation.
但如果你是用malloc,LocalAlloc,GlobalAlloc這類api分配空間,就需要指定具體的字節數:
pBuffer = (TCHAR*) malloc (128 * sizeof(TCHAR) );