標籤:
上一篇文章帶你玩轉Visual Studio——帶你跳出坑爹的Runtime Library坑幫我們理解了Windows中的各種類型C/C++執行階段程式庫及它的來龍去脈,這是C++開發中特別容易誤入歧途的一個地方,我們對它進行了總結和歸納。本篇文章我們將繼續講解C++開發中容易混淆的另一個概念——多位元組字元集與Unicode字元集。
多位元組字元與寬位元組字元char與wchar_t
我們知道C++基礎資料型別 (Elementary Data Type)中表示字元的有兩種:char、wchar_t。
char叫多位元組字元,一個char佔一個位元組,之所以叫多位元組字元是因為它表示一個字時可能是一個位元組也可能是多個位元組。一個英文字元(如’s’)用一個char(一個位元組)表示,一個中文漢字(如’中’)用3個char(三個位元組)表示,看下面的例子。
void TestChar(){ char ch1 = ‘s‘; // 正確 cout << "ch1:" << ch1 << endl; char ch2 = ‘中‘; // 錯誤,一個char不能完整存放一個漢字資訊 cout << "ch2:" << ch2 << endl; char str[4] = "中"; //前三個位元組存放漢字‘中‘,最後一個位元組存放字串結束符\0 cout << "str:" << str << endl; //char str2[2] = "國"; // 錯誤:‘str2‘ : array bounds overflow //cout << str2 << endl;}
結點如下:
ch1:s
ch2:
str:中
wchar_t被稱為寬字元,一個wchar_t佔2個位元組。之所以叫寬字元是因為所有的字都要用兩個位元組(即一個wchar_t)來表示,不管是英文還是中文。看下面的例子:
void TestWchar_t(){ wcout.imbue(locale("chs")); // 將wcout的本地化語言設定為中文 wchar_t wch1 = L‘s‘; // 正確 wcout << "wch1:" << wch1 << endl; wchar_t wch2 = L‘中‘; // 正確,一個漢字用一個wchar_t表示 wcout << "wch2:" << wch2 << endl; wchar_t wstr[2] = L"中"; // 前兩個位元組(前一個wchar_t)存放漢字‘中‘,最後兩個位元組(後一個wchar_t)存放字串結束符\0 wcout << "wstr:" << wstr << endl; wchar_t wstr2[3] = L"中國"; wcout << "wstr2:" << wstr2 << endl;}
結果如下:
ch1:s
ch2:中
str:中
str2:中國
說明:
1. 用常量字元給wchar_t變數賦值時,前面要加L。如: wchar_t wch2 = L’中’;
2. 用常量字串給wchar_t數組賦值時,前面要加L。如: wchar_t wstr2[3] = L”中國”;
3. 如果不加L,對於英文可以正常,但對於非英文(如中文)會出錯。
string與wstring
字元數組可以表示一個字串,但它是一個定長的字串,我們在使用之前必須知道這個數組的長度。為方便字串的操作,STL為我們定義好了字串的類string和wstring。大家對string肯定不陌生,但wstring可能就用的少了。
string是普通的多位元組版本,是基於char的,對char數組進行的一種封裝。
wstring是Unicode版本,是基於wchar_t的,對wchar_t數組進行的一種封裝。
string 與 wstring的相關轉換:
以下的兩個方法是跨平台的,可在Windows下使用,也可在Linux下使用。
#include <cstdlib>#include <string.h>#include <string>// wstring => stringstd::string WString2String(const std::wstring& ws){ std::string strLocale = setlocale(LC_ALL, ""); const wchar_t* wchSrc = ws.c_str(); size_t nDestSize = wcstombs(NULL, wchSrc, 0) + 1; char *chDest = new char[nDestSize]; memset(chDest,0,nDestSize); wcstombs(chDest,wchSrc,nDestSize); std::string strResult = chDest; delete []chDest; setlocale(LC_ALL, strLocale.c_str()); return strResult;}// string => wstringstd::wstring String2WString(const std::string& s){ std::string strLocale = setlocale(LC_ALL, ""); const char* chSrc = s.c_str(); size_t nDestSize = mbstowcs(NULL, chSrc, 0) + 1; wchar_t* wchDest = new wchar_t[nDestSize]; wmemset(wchDest, 0, nDestSize); mbstowcs(wchDest,chSrc,nDestSize); std::wstring wstrResult = wchDest; delete []wchDest; setlocale(LC_ALL, strLocale.c_str()); return wstrResult;}
字元集(Charcater Set)與字元編碼(Encoding)
字元集(Charcater Set或Charset):是一個系統支援的所有抽象字元的集合,也就是一系列字元的集合。字元是各種文字和符號的總稱,包括各國家文字、標點符號、圖形符號、數字等。常見的字元集有:ASCII字元集、GB2312字元集(主要用於處理中文漢字)、GBK字元集(主要用於處理中文漢字)、Unicode字元集等。
字元編碼(Character Encoding):是一套法則,使用該法則能夠對自然語言的字元的一個字元集(如字母表或音節表),與電腦能識別的位元字進行配對。即它能在符號集合與數字系統之間建立對應關係,是資訊處理的一項基本技術。通常人們用符號集合(一般情況下就是文字)來表達資訊,而電腦的資訊處理系統則是以二進位的數字來儲存和處理資訊的。字元編碼就是將符號轉換為電腦能識別的二進位編碼。
一般一個字元集等同於一個編碼方式,ANSI體系(ANSI是一種字元代碼,為使電腦支援更多語言,通常使用 0x80~0xFF 範圍的 2 個位元組來表示 1 個字元)的字元集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。一般我們說一種編碼都是針對某一特定的字元集。
一個字元集上也可以有多種編碼方式,例如UCS字元集(也是Unicode使用的字元集)上有UTF-8、UTF-16、UTF-32等編碼方式。
從電腦字元編碼的發展曆史角度來看,大概經曆了三個階段:
第一個階段:ASCII字元集和ASCII編碼。
電腦剛開始只支援英語(即拉丁字元),其它語言不能夠在電腦上儲存和顯示。ASCII用一個位元組(Byte)的7位(bit)表示一個字元,第一位置0。後來為了表示更多的歐洲常用字元又對ASCII進行了擴充,又有了EASCII,EASCII用8位表示一個字元,使它能多表示128個字元,支援了部分西歐字元。
第二個階段:ANSI編碼(本地化)
為使電腦支援更多語言,通常使用 0x80~0xFF 範圍的 2 個位元組來表示 1 個字元。比如:漢字 ‘中’ 在中文作業系統中,使用 [0xD6,0xD0] 這兩個位元組儲存。
不同的國家和地區制定了不同的標準,由此產生了 GB2312, BIG5, JIS 等各自的編碼通訊協定。這些使用 2 個位元組來代表一個字元的各種漢字延伸編碼方式,稱為 ANSI 編碼。在簡體中文系統下,ANSI 編碼代表 GB2312 編碼,在日文作業系統下,ANSI 編碼代表 JIS 編碼。
不同 ANSI 編碼之間互不相容,當資訊在國際間交流時,無法將屬於兩種語言的文字,儲存在同一段 ANSI 編碼的文本中。
第三個階段:UNICODE(國際化)
為了使國際間資訊交流更加方便,國際組織制定了 UNICODE 字元集,為各種語言中的每一個字元設定了統一併且唯一的數字編號,以滿足跨語言、跨平台進行文本轉換、處理的要求。UNICODE 常見的有三種編碼方式:UTF-8(1個位元組表示)、UTF-16((2個位元組表示))、UTF-32(4個位元組表示)。
我們可以用一個樹狀圖來表示由ASCII發展而來的各個字元集和編碼的分支:
圖 1: 各種類型的編譯
如果要更詳細地瞭解字元集和字元編碼請參考:
字元集和字元編碼(Charset & Encoding)
工程裡多位元組與寬字元的配製
右鍵你的工程名->Properties,設定如下:
圖 2: Character Set
- 當設定為Use Unicode Character Set時,會有先行編譯宏:_UNICODE、UNICODE
圖 3: Unicode
- 當設定為Use Multi-Byte Character Set時,會有先行編譯宏:_MBCS
圖 4: Multi-Byte
Unicode Character Set與Multi-Byte Character Set (多位元組字元集 (MBCS)有什麼區別呢?
Unicode Character Set和Multi-Byte Character Set這兩個設定有什麼區別呢?我們來看一個例子:
有一個程式需要用MessageBox彈出提示框:
#include "windows.h"void TestMessageBox(){ ::MessageBox(NULL, "這是一個測試程式!", "Title", MB_OK);}
上面這個Demo非常簡單不用多說了吧!我們將Character Set設定為Multi-Byte Character Set時,可以正常編譯和運行。但當我們設定為Unicode Character Set,則會有以下編譯錯誤:
error C2664: ‘MessageBoxW’ : cannot convert parameter 2 from ‘const char [18]’ to ‘LPCWSTR’
這是因為MessageBox有兩個版本,一個MessageBoxW針對Unicode版的,一個是MessageBoxA針對Multi-Byte的,它們通過不同宏進行隔開,預設不同的宏會使用不同的版本。我們使用了Use Unicode Character Set就預設了_UNICODE、UNICODE宏,所以編譯時間就會使用MessageBoxW,這時我們傳入多位元組常量字串肯定會有問題,而應該傳入寬符的字串,即將”Title”改為L”Title”就可以了,”這是一個測試程式!”也一樣。
WINUSERAPIintWINAPIMessageBoxA( __in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption, __in UINT uType);WINUSERAPIintWINAPIMessageBoxW( __in_opt HWND hWnd, __in_opt LPCWSTR lpText, __in_opt LPCWSTR lpCaption, __in UINT uType);#ifdef UNICODE#define MessageBox MessageBoxW#else#define MessageBox MessageBoxA#endif // !UNICODE
上面的Multi-Byte Character Set一般是指ANSI(多位元組)字元集,關於ANSI請參考第二小節字元集(Charcater Set)與字元編碼(Encoding)。而Unicode Character Set就是Unicode字元集,一般是指UTF-16編碼的Unicode。也就是說每個字元編碼為兩個位元組,兩個位元組可以表示65535個字元,65535個字元可以表示世界上大部分的語言。
一般推薦使用Unicode的方式,因為它可以適應各個國家語言,在進行軟體國際時將會非常便得。除非在對儲存要求非常高的時候,或要相容C的代碼時,我們才會使用多位元組的方式 。
理解_T()、_Text()宏即L”“
上一小節對MessageBox的調用中除了使用L”Title”外,還可以使用_T(“Title”)和_TEXT(“Title”)。而且你會發現在MFC和Win32程式中會更多地使用_T和_TEXT,那_T、_TEXT和L之間有什麼區別呢?
通過第一小節多位元組字元與寬位元組字元我們知道表示多位元組字元(char)串常量時用一般的雙引號括起來就可以了,如”String test”;而表示寬位元組字元(wchar_t)串常量時要在引號前加L,如L”String test”。
查看tchar.h標頭檔的定義我們知道_T和_TEXT的功能是一樣的,是一個預定義的宏。
#define _T(x) __T(x)#define _TEXT(x) __T(x)
我們再看看__T(x)的定義,發現它有兩個:
#ifdef _UNICODE// ... 省略其它代碼#define __T(x) L ## x// ... 省略其它代碼#else /* ndef _UNICODE */// ... 省略其它代碼#define __T(x) x// ... 省略其它代碼#endif /* _UNICODE */
這下明白了嗎?當我們的工程的Character Set設定為Use Unicode Character Set時_T和_TEXT就會在常量字串前面加L,否則(即Use Multi-Byte Character Set時)就會以一般的字串處理。
Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR
VC++中還有一些常用的宏你也許會範糊塗,如Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR。這裡我們統一總結一下:
常見的宏:
類型 |
MBCS |
UNICODE |
WCHAR |
wchar_t |
wchar_t |
LPSTR |
char* |
char* |
LPCSTR |
const char* |
const char* |
LPWSTR |
wchar_t* |
wchar_t* |
LPCWSTR |
const wchar_t* |
const wchar_t* |
TCHAR |
char |
wchar_t |
LPTSTR |
TCHAR*(或char*) |
TCHAR* (或wchar_t*) |
LPCTSTR |
const TCHAR* |
const TCHAR* |
相互轉換方法:
LPWSTR->LPTSTR: W2T();
LPTSTR->LPWSTR: T2W();
LPCWSTR->LPCSTR: W2CT();
LPCSTR->LPCWSTR: T2CW();
ANSI->UNICODE: A2W();
UNICODE->ANSI: W2A();
字串函數:
還有一些字串的操作函數,它們也有一 一對應關係:
MBCS |
UNICODE |
strlen(); |
wcslen(); |
strcpy(); |
wcscpy(); |
strcmp(); |
wcscmp(); |
strcat(); |
wcscat(); |
strchr(); |
wcschr(); |
… |
… |
通過這些函數和宏的命名你也許就發現了一些霍規律,一般帶有首碼w(或尾碼W)的都是用於寬字元的,而不帶首碼w(或帶有尾碼A)的一般是用於多位元組字元的。
理解CString產生的原因與工作的機理
CString:動態TCHAR數組,是對TCHAR數組的一種封閉。它是一個完全獨立的類,封裝了“+”等操作符和字串操作方法,換句話說就是CString是對TCHAR操作的方法的集合。它的作用是方便WIN32程式和MFC程式進行字串的處理和類型的轉換。
關於CString更詳細的用法請參考:
CString與string、char*的區別和轉換
CString的常見用法
參考文章:
字元集和字元編碼(Charset & Encoding)
字元,位元組和編碼
《windows核心編程系列》二談談ANSI和Unicode字元集
Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR
轉自:http://blog.csdn.net/luoweifu/article/details/49382969
轉: 帶你玩轉Visual Studio——帶你理解多位元組編碼與Unicode碼