上一篇文章[帶你玩轉Visual Studio——帶你跳出坑爹的Runtime Library坑](http://blog.csdn.net/luoweifu/article/details/49055933#comments)幫我們理解了Windows中的各種類型C/C++運行時庫及它的來龍去脈,這是C++開發中特別容易誤入歧途的一個地方,我們對它進行了總結和歸納。本篇文章我們將繼續講解C++開發中容易混淆的另一個概念——多字節字符集與Unicode字符集。
# 多字節字符與寬字節字符
## char與wchar_t
我們知道C++基本數據類型中表示字符的有兩種: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 => string
std::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 => wstring
std::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)](http://blog.csdn.net/luoweifu/article/details/49385121)
# 工程里多字節與寬字符的配制
右鍵你的工程名->Properties,設置如下:?
?
圖 2: Character Set
1. 當設置為Use Unicode Character Set時,會有預編譯宏:_UNICODE、UNICODE?

圖 3: Unicode
2. 當設置為Use Multi-Byte Character Set時,會有預編譯宏:_MBCS?

圖 4: Multi-Byte
## Unicode Character Set與Multi-Byte Character Set有什么區別呢?
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”就可以了,”這是一個測試程序!”也一樣。
~~~
WINUSERAPI
int
WINAPI
MessageBoxA(
__in_opt HWND hWnd,
__in_opt LPCSTR lpText,
__in_opt LPCSTR lpCaption,
__in UINT uType);
WINUSERAPI
int
WINAPI
MessageBoxW(
__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)](http://blog.csdn.net/luoweifu/article/details/49382969#t4)。而Unicode Character Set就是Unicode字符集,一般是指UTF-16編碼的Unicode。也就是說每個字符編碼為兩個字節,兩個字節可以表示65535個字符,65535個字符可以表示世界上大部分的語言。
一般推薦使用Unicode的方式,因為它可以適應各個國家語言,在進行軟件國際時將會非常便得。除非在對存儲要求非常高的時候,或要兼容C的代碼時,我們才會使用多字節的方式 。
# 理解_T()、_Text()宏即L”“
[上一小節](http://blog.csdn.net/luoweifu/article/details/49382969#t6)對MessageBox的調用中除了使用L”Title”外,還可以使用_T(“Title”)和_TEXT(“Title”)。而且你會發現在MFC和Win32程序中會更多地使用_T和_TEXT,那_T、_TEXT和L之間有什么區別呢?
通過第一小節[多字節字符與寬字節字符](http://blog.csdn.net/luoweifu/article/details/49382969#t0)我們知道表示多字節字符(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*的區別和轉換](http://blog.csdn.net/luoweifu/article/details/20232379)?
[CString的常見用法](http://www.cnblogs.com/Caiqinghua/archive/2009/02/16/1391190.html)
* * *
* * *
參考文章:?
[字符集和字符編碼(Charset & Encoding)](http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html)?
[字符,字節和編碼](http://www.regexlab.com/zh/encoding.htm)?
[《windows核心編程系列》二談談ANSI和Unicode字符集](http://blog.csdn.net/ithzhang/article/details/7916732)?
[Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR](http://www.cnblogs.com/goed/archive/2011/11/11/2245702.html)
上一篇回顧:?
[帶你玩轉Visual Studio——帶你跳出坑爹的Runtime Library坑](http://blog.csdn.net/luoweifu/article/details/49055933#comments)
下一篇要講述的內容:?
帶你玩轉Visual Studio——帶你解讀__cdecl、__fastcall、__stdcall三種調用約定