1. 程式人生 > >寬位元組與窄位元組(Ansi與Unicode)

寬位元組與窄位元組(Ansi與Unicode)

①、Ansi與Unicode簡要說明及各自的優缺點:
他們是兩種字元的編碼格式,Ansi=窄位元組,Unicode=寬位元組,Ansi用char格式表示一個字元,佔用一個位元組的儲存空間,最多表示255個字元,
表示英文還可以,但對於中文、日文、韓文等語言來說就不夠用了,所以如果你的程式是Ansi編碼的話,
那麼你寫的中文語言的程式拿到日文、韓文等系統上面就會出現亂碼。所以有了Unicode,用二個位元組去表示一個字元,格式是 unsigned short,被定義成 wchar_t 格式
這樣就可以表示世界上絕大多數的語言了!但有利就有弊,缺點呢?就是空間佔用翻倍了,網路傳輸的資料量也增大了……


◆ vc++ 6.0 預設為Ansi編碼,vs2005、vs2008、vs2010 等預設都是Unicode編碼,當然可以進行工程的設定從而進行編碼的轉換,見演示!


◆ 就我個人觀點:還是建議大家使用Unicode寬位元組的編碼格式,具體見下面:


◆ 系統提供了兩種型別的 API 函式,見:user32.dll 中的 MessageBox 函式,其實 MessageBox 他只是一個巨集,他對應的兩個版本的函式分別為:MessageBoxA 和 MessageBoxW,你在使用的時候系統會根據是否定義了_UNICODE 巨集來進行判斷該使用哪個版本的函式!如果你的工程沒有定義_UNICODE 巨集,那麼就使用窄位元組的 MessageBoxA,如果定義了,那麼就使用寬位元組的 MessageBoxW,具體在vs2008中,右鍵找定義,見演示!


◆ 網摘:Windows 2000 及其以後的 Xp、2003、Vista、Win7 等系統都是使用Unicode從頭進行開發的,如果呼叫任何一個Windows API 函式並給它傳遞一個 ANSI 字串,那麼系統首先要將字串轉換成Unicode,然後將Unicode字串傳遞給作業系統。如果希望函式返回ANSI字串,系統就會先將Unicode字串轉換成ANSI字串,然後將結果返回給你的應用程式。進行這些字串的轉換需要佔用系統的時間和記憶體。通過從頭開始用Unicode來開發應用程式,就能夠使你的應用程式更加高效的執行!


==================================================================


②、不同編碼格式下的字串處理及相互轉化:


◆ 大家在程式設計時經常遇到的資料型別:
● Ansi:
char、char * 、const char *
CHAR、(PCHAR、PSTR、LPSTR)、LPCSTR


● Unicode:
wchar_t、wchar_t * 、const wchar_t *
WCHAR、(PWCHAR、PWSTR、LPWSTR)、LPCWSTR


● T 通用型別:
TCHAR、(TCHAR * 、PTCHAR、PTSTR、LPTSTR)、LPCTSTR


以上,其中:P代表指標的意思,STR代表字串的意思,L是長指標的意思,在WIN32平臺下可以忽略,C代表const常量的意思,W代表wide寬位元組的意思,T大家可以理解為通用型別的意思,
就是可以根據工程中是否定義_UNICODE 巨集,分別定義成不同的型別,比如:TCHAR 型別,如果工程中定義了_UNICODE 巨集,那麼他最終被定義成 wchar_t 型別,
如果工程中沒有定義_UNICODE 巨集,那麼 TCHAR 被最終定義成 char 型別。


〓※※※〓 其方便性就是修改了工程的編碼格式之後不用修改程式碼,所以還是建議大家在編寫程式的時候使用通用型別!


@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@


◆ 字串型別的物件的定義:
● Ansi:char *pAnsiStr = "hello";
● Unicode:wchar_t *pUnicodeStr = L"hello";
● 通用型別:TCHAR *pTStr = _T("hello"); 或者 TCHAR *pTStr = _TEXT("hello");
● 動態申請記憶體:TCHAR *pszBuf = new TCHAR[100];


其中,_TEXT 和 _T 是一樣的,定義如下:
#define _T(x)       __T(x)
#define _TEXT(x)    __T(x)


來看看 __T 的最終定義:
#ifdef  _UNICODE
#define __T(x)      L##x
#else
#define __T(x)      x
#endif


其中,##為連線起來的意思。


@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@


◆ 常用的字串處理函式,具體資訊見MSDN:
字串長度:
● Ansi:strlen(char *str);
● Unicode:wcslen(wchar_t *str);
● 通用函式:_tcslen(TCHAR *str);


● Ansi:int atoi(const char *str);
● Unicode:int _wtoi(const wchar_t *str);
● 通用函式:_tstoi(const TCHAR *str);


字串拷貝:
● Ansi:strcpy(char *strDestination, const char *strSource);
● Unicode:wcscpy(wchar_t *strDestination, const wchar_t *strSource);
● 通用函式:_tcscpy(TCHAR *strDestination, const TCHAR *strSource);


以上函式不安全,在vs2003等以上版本的編譯器中會有warnning警告提示,以下為安全函式(vc++6.0不支援):
● Ansi:strcpy_s(char *strDestination, size_t numberOfElements, const char *strSource);
● Unicode:wcscpy_s(wchar_t *strDestination, size_t numberOfElements, const wchar_t *strSource);
● 通用函式:_tcscpy_s(TCHAR *strDestination, size_t numberOfElements, const TCHAR *strSource);


numberOfElements
Size of the destination string buffer. 目的緩衝區的大小,以位元組為單位,不是字元!


size_t unsigned integer,在MSDN中的解釋:Result of sizeof operator,也就是說 size_t 是 unsigned integer 即無符號整數。那為什麼會有size_t這個型別呢?
因為不同平臺的作業系統(32/64)中 int/long 等型別所佔的位元組並不一樣,而 size_t 在不同的平臺下有不同的定義。有點類似於TCHAR型別:
#ifndef   _SIZE_T_DEFINED
  #ifdef     _WIN64
  typedef   unsigned   __int64         size_t;   //8個位元組,64位
  #else
  typedef   _W64   unsigned   int       size_t;   //4個位元組,32位
  #endif
  #define   _SIZE_T_DEFINED
#endif


@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@


◆ Ansi 與 Unicode 字串型別的互相轉換:
上面給大家介紹的都是窄位元組就是窄位元組,寬位元組就是寬位元組,那麼下面就給大家介紹下他們兩個之間的轉換。
在程式中還是不建議大家來回來去的進行字串編碼的轉換,要麼就都使用Ansi,要麼就都使用Unicode,
但是呢,往往有些 API 函式只提供了窄位元組版本(比如:GetProcAddress,見MSDN)或者只提供寬位元組版本(比如:CommandLineToArgvW,見MSDN),
這個時候就要進行字串編碼格式的轉換了。


但是,這裡提醒下大家,不是所有的都需要轉換,有一些是不需要轉換的,比如 socket 中的 send 或者 recv 函式,大家要明白函式引數的真正意義!
=================================================
◆ 字串佔用位元組數:
● Ansi:
char szStr[] = "abc";
佔用位元組數求法:sizeof(szStr);


char *psz = "defgh";
佔用位元組數求法:strlen(psz)*sizeof(char);


● Unicode:
wchar_t szwStr[] = L"abc";
佔用位元組數求法:sizeof(szwStr);


wchar_t *pwsz = L"defgh";
佔用位元組數求法:wcslen(pwsz)*sizeof(wchar_t);


● 通用函式:
TCHAR szStr[] = _T("abc");
佔用位元組數求法:sizeof(szStr);


TCHAR *psz = _T("defgh");
佔用位元組數求法:_tcslen(psz)*sizeof(TCHAR);
=======================================================


● 轉換用到的最根本的 API 函式:
WideCharToMultiByte 實現寬位元組轉換到窄位元組
MultiByteToWideChar 實現窄位元組轉換到寬位元組


WideCharToMultiByte 的內碼表用來標記與新轉換的字串相關的內碼表;
MultiByteToWideChar 的內碼表用來標記與一個多位元組字串相關的內碼表,


[1]、常用的內碼表有 CP_ACP 和 CP_UTF8 兩個:
使用 CP_ACP 內碼表就實現了 ANSI 與 Unicode 之間的轉換;--- 我們所用的!
使用 CP_UTF8 內碼表就實現了 UTF-8 與 Unicode 之間的轉換。


[2]、dwFlags 引數允許我們進行額外的控制,但是,一般情況下都不使用這個標誌,直接傳遞 0 就行了。


[3]、lpDefaultChar和pfUsedDefaultChar:只有當WideCharToMultiByte函式遇到一個寬位元組字元,而該字元在uCodePage引數標識的內碼表中並沒有它的表示法時,WideCharToMultiByte函式才使用這兩個引數。如果寬位元組字元不能被轉換,該函式便使用lpDefaultChar引數指向的字元。如果該引數是NULL(這是大多數情況下的引數值),那麼該函式使用系統的預設字元。該預設字元通常是個問號。這對於檔名來說是危險的,因為問號是個萬用字元。pfUsedDefaultChar引數指向一個布林變數,如果Unicode字串中至少有一個字元不能轉換成等價多位元組字元,那麼函式就將該變數置為TRUE。如果所有字元均被成功地轉換,那麼該函式就將該變數置為FALSE。當函式返回以便檢查寬位元組字串是否被成功地轉換後,可以測試該變數。 


● 兩個轉換函式的使用舉例:
char *cctryWideCharToAnsi(wchar_t *pWideChar)
{
if (!pWideChar) return NULL;
char *pszBuf = NULL;
int needBytes = WideCharToMultiByte(CP_ACP, 0, pWideChar, -1, NULL, 0, NULL, NULL);
if (needBytes > 0){
pszBuf = new char[needBytes+1];
ZeroMemory(pszBuf, (needBytes+1)*sizeof(char));
WideCharToMultiByte(CP_ACP, 0, pWideChar, -1, pszBuf, needBytes, NULL, NULL);
}


return pszBuf;
}


wchar_t *cctryAnsiCharToWide(char *pChar)
{
if (!pChar) return NULL;
wchar_t *pszBuf = NULL;
int needWChar = MultiByteToWideChar(CP_ACP, 0, pChar, -1, NULL, 0);
if (needWChar > 0){
pszBuf = new wchar_t[needWChar+1];
ZeroMemory(pszBuf, (needWChar+1)*sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, pChar, -1, pszBuf, needWChar);
}


return pszBuf;
}


〓※※※〓 使用過後千萬別忘記釋放空間……


● A2W、W2A、T2A、T2W 巨集的使用以及注意事項:
[1]、使用 alloca() 函式進行空間的申請,巨集返回的地址空間是從棧上面申請的,那麼以後就不必釋放,這樣就涉及到了一個作用域的問題,具體見MSDN,
大家可以簡單的理解為“向下相容”,給大家解釋解釋!
[2]、不要在一個函式的迴圈體中使用 A2W 等字元轉換巨集,可能引起棧溢位。
具體課參照這篇帖子:http://www.cctry.com/thread-17948-1-1.html
比如:
#include <atlconv.h>
void func()
{
    while(true)
    {
        {
            USES_CONVERSION;
            testFunc(A2W("abc"));
        }
    }
}