?? c++字符串完全指南 - win32字符編碼(一) .txt
字號(hào):
C++字符串完全指南 - Win32字符編碼(一)
作者: 翻譯:連波
Thursday, November 14 2002 6:11 PM
前言
字符串的表現(xiàn)形式各異,象TCHAR,std::string,BSTR等等,有時(shí)還會(huì)見到怪怪的用_tcs起頭的宏。這個(gè)指南的目的就是說明各種字符串類型及其用途,并說明如何在必要時(shí)進(jìn)行類型的相互轉(zhuǎn)換。
在指南的第一部分,介紹三種字符編碼格式。理解編碼的工作原理是致為重要的。即使你已經(jīng)知道字符串是一個(gè)字符的數(shù)組這樣的概念,也請閱讀本文,它會(huì)讓你明白各種字符串類之間的關(guān)系。
指南的第二部分,將闡述各個(gè)字符串類,什么時(shí)候使用哪種字符串類,及其相互轉(zhuǎn)換。
字符串基礎(chǔ) - ASCII, DBCS, Unicode
所有的字符串類都起源于C語言的字符串,而C語言字符串則是字符的數(shù)組。首先了解一下字符類型。有三種編碼方式和三種字符類型。
第一種編碼方式是單字節(jié)字符集,稱之為SBCS,它的所有字符都只有一個(gè)字節(jié)的長度。ASCII碼就是SBCS。SBCS字符串由一個(gè)零字節(jié)結(jié)尾。
第二種編碼方式是多字節(jié)字符集,稱之為MBCS,它包含的字符中有單字節(jié)長的字符,也有多字節(jié)長的字符。Windows用到的MBCS只有二種字符類型,單字節(jié)字符和雙字節(jié)字符。因此Windows中用得最多的字符是雙字節(jié)字符集,即DBCS,通常用它來代替MBCS。
在DBCS編碼中,用一些保留值來指明該字符屬于雙字節(jié)字符。例如,Shift-JIS(通用日語)編碼中,值0x81-0x9F 和 0xE0-0xFC 的意思是:“這是一個(gè)雙字節(jié)字符,下一個(gè)字節(jié)是這個(gè)字符的一部分”。這樣的值通常稱為前導(dǎo)字節(jié)(lead byte),總是大于0x7F。前導(dǎo)字節(jié)后面是跟隨字節(jié)(trail byte)。DBCS的跟隨字節(jié)可以是任何非零值。與SBCS一樣,DBCS字符串也由一個(gè)零字節(jié)結(jié)尾。
第三種編碼方式是Unicode。Unicode編碼標(biāo)準(zhǔn)中的所有字符都是雙字節(jié)長。有時(shí)也將Unicode稱為寬字符集(wide characters),因?yàn)樗淖址葐巫止?jié)字符更寬(使用更多內(nèi)存)。注意,Unicode不是MBCS - 區(qū)別在于MBCS編碼中的字符長度是不同的。Unicode字符串用二個(gè)零字節(jié)字符結(jié)尾(一個(gè)寬字符的零值編碼)。
單字節(jié)字符集是拉丁字母,重音文字,用ASCII標(biāo)準(zhǔn)定義,用于DOS操作系統(tǒng)。雙字節(jié)字符集用于東亞和中東語言。Unicode用于COM和Windows NT內(nèi)部。
讀者都很熟悉單字節(jié)字符集,它的數(shù)據(jù)類型是char。雙字節(jié)字符集也使用char數(shù)據(jù)類型(雙字節(jié)字符集中的許多古怪處之一)。Unicode字符集用wchar_t數(shù)據(jù)類型。Unicode字符串用L前綴起頭,如:
wchar_t wch = L'1'; // 2 個(gè)字節(jié), 0x0031
wchar_t* wsz = L"Hello"; // 12 個(gè)字節(jié), 6 個(gè)寬字符
字符串的存儲(chǔ)
單字節(jié)字符串順序存放各個(gè)字符,并用零字節(jié)表示字符串結(jié)尾。例如,字符串"Bob"的存儲(chǔ)格式為:
Unicode編碼中,L"Bob"的存儲(chǔ)格式為:
用0x0000 (Unicode的零編碼)結(jié)束字符串。
DBCS 看上去有點(diǎn)象SBCS。以后我們會(huì)看到在串處理和指針使用上是有微妙差別的。字符串"日本語" (nihongo) 的存儲(chǔ)格式如下(用LB和TB分別表示前導(dǎo)字節(jié)和跟隨字節(jié)):
注意,"ni"的值不是WORD值0xFA93。值93和FA順序組合編碼為字符"ni"。(在高位優(yōu)先CPU中,存放順序正如上所述)。
字符串處理函數(shù)
C語言字符串處理函數(shù),如strcpy(), sprintf(), atol()等只能用于單字節(jié)字符串。在標(biāo)準(zhǔn)庫中有只用于Unicode字符串的函數(shù),如wcscpy(), swprintf(), _wtol()。
微軟在C運(yùn)行庫(CRT)中加入了對DBCS字符串的支持。對應(yīng)于strxxx()函數(shù),DBCS使用_mbsxxx()函數(shù)。在處理DBCS字符串(如日語,中文,或其它DBCS)時(shí),就要用_mbsxxx()函數(shù)。這些函數(shù)也能用于處理SBCS字符串(因?yàn)镈BCS字符串可能就只含有單字節(jié)字符)。
現(xiàn)在用一個(gè)示例來說明字符串處理函數(shù)的不同。如有Unicode字符串L"Bob":
x86 CPU的排列順序是低位優(yōu)先(little-endian)的,值0x0042的存儲(chǔ)順序?yàn)?2 00。這時(shí)如用strlen()函數(shù)求字符串的長度就發(fā)生問題。函數(shù)找到第一個(gè)字節(jié)42,然后是00,意味著字符串結(jié)尾,于是返回1。反之,用wcslen()函數(shù)求"Bob"的長度更糟糕。wcslen()首先找到0x6F42,然后是0x0062,以后就在內(nèi)存緩沖內(nèi)不斷地尋找00 00直至發(fā)生一般性保護(hù)錯(cuò)(GPF)。
strxxx()及其對應(yīng)的_mbsxxx()究竟是如何運(yùn)作的?二者之間的不同是非常重要的,直接影響到正確遍歷DBCS字符串的方法。下面先介紹字符串遍歷,然后再回來討論strxxx()和 _mbsxxx()。
字符串遍歷
我們中的大多數(shù)人都是從SBCS成長過來的,都習(xí)慣于用指針的 ++ 和 -- 操作符來遍歷字符串,有時(shí)也使用數(shù)組來處理字符串中的字符。這二種方法對于SBCS 和 Unicode 字符串的操作都是正確無誤的,因?yàn)槎叩淖址际堑乳L的,編譯器能夠的正確返回我們尋求的字符位置。
但對于DBCS字符串就不能這樣了。用指針訪問DBCS字符串有二個(gè)原則,打破這二個(gè)原則就會(huì)造成錯(cuò)誤。
1. 不可使用 ++ 算子,除非每次都檢查是否為前導(dǎo)字節(jié)。
2. 絕不可使用 -- 算子來向后遍歷。
先說明原則2,因?yàn)楹苋菀渍业揭粋€(gè)非人為的示例。假設(shè),有一個(gè)配制文件,程序啟動(dòng)時(shí)要從安裝路徑讀取該文件,如:C:\Program Files\MyCoolApp\config.bin。文件本身是正常的。
假設(shè)用以下代碼來配制文件名:
bool GetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];
// 這里從注冊表讀取文件的安裝路徑,假設(shè)一切正常。
// 如果路徑末尾沒有反斜線,就加上反斜線。
// 首先,用指針指向結(jié)尾零:
char* pLastChar = strchr ( szConfigFilename, '\0' );
// 然后向后退一個(gè)字符:
pLastChar--;
if ( *pLastChar != '\\' )
strcat ( szConfigFilename, "\\" );
// 加上文件名:
strcat ( szConfigFilename, "config.bin" );
// 如果字符串長度足夠,返回文件名:
if ( strlen ( szConfigFilename ) >= nBuffSize )
return false;
else
{
strcpy ( pszName, szConfigFilename );
return true;
}
}
這段代碼的保護(hù)性是很強(qiáng)的,但用到DBCS字符串還是會(huì)出錯(cuò)。假如文件的安裝路徑用日語表達(dá):C:\ヨウユソ,該字符串的內(nèi)存表達(dá)為:
這時(shí)用上面的GetConfigFileName()函數(shù)來檢查文件路徑末尾是否含有反斜線就會(huì)出錯(cuò),得到錯(cuò)誤的文件名。
錯(cuò)在哪里?注意上面的二個(gè)十六進(jìn)制值0x5C(藍(lán)色)。前面的0x5C是字符"\",后面則是字符值83 5C,代表字符"ソ"。可是函數(shù)把它誤認(rèn)為反斜線了。
正確的方法是用DBCS函數(shù)將指針指向恰當(dāng)?shù)淖址恢茫缦滤荆?
bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];
// 這里從注冊表讀取文件的安裝路徑,假設(shè)一切正常。
// 如果路徑末尾沒有反斜線,就加上反斜線。
// 首先,用指針指向結(jié)尾零:
char* pLastChar = _mbschr ( szConfigFilename, '\0' );
// 然后向后退一個(gè)雙字節(jié)字符:
pLastChar = CharPrev ( szConfigFilename, pLastChar );
if ( *pLastChar != '\\' )
_mbscat ( szConfigFilename, "\\" );
// 加上文件名:
_mbscat ( szConfigFilename, "config.bin" );
// 如果字符串長度足夠,返回文件名:
if ( _mbslen ( szInstallDir ) >= nBuffSize )
return false;
else
{
_mbscpy ( pszName, szConfigFilename );
return true;
}
}
這個(gè)改進(jìn)的函數(shù)用CharPrev() API 函數(shù)將指針pLastChar向后移動(dòng)一個(gè)字符。如果字符串末尾的字符是雙字節(jié)字符,就向后移動(dòng)2個(gè)字節(jié)。這時(shí)返回的結(jié)果是正確的,因?yàn)椴粫?huì)將字符誤判為反斜線。
現(xiàn)在可以想像到第一原則了。例如,要遍歷字符串尋找字符":",如果不使用CharNext()函數(shù)而使用++算子,當(dāng)跟隨字節(jié)值恰好也是":"時(shí)就會(huì)出錯(cuò)。
與原則2相關(guān)的是數(shù)組下標(biāo)的使用:
2a. 絕不可在字符串?dāng)?shù)組中使用遞減下標(biāo)。
出錯(cuò)原因與原則2相同。例如,設(shè)置指針pLastChar為:
char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];
結(jié)果與原則2的出錯(cuò)一樣。下標(biāo)減1就是指針向后移動(dòng)一個(gè)字節(jié),不符原則2。
再談strxxx() 與_mbsxxx()
現(xiàn)在可以清楚為什么要用 _mbsxxx() 函數(shù)了。strxxx() 函數(shù)不認(rèn)識(shí)DBCS字符而 _mbsxxx()認(rèn)識(shí)。如果調(diào)用strrchr("C:\\", '\\')函數(shù)可能會(huì)出錯(cuò),但 _mbsrchr()認(rèn)識(shí)雙字節(jié)字符,所以能返回指向最后出現(xiàn)反斜線字符的指針位置。
最后提一下strxxx() 和 _mbsxxx() 函數(shù)族中的字符串長度測量函數(shù),它們都返回字符串的字節(jié)數(shù)。如果字符串含有3個(gè)雙字節(jié)字符,_mbslen()將返回6。而Unicode的函數(shù)返回的是wchar_ts的數(shù)量,如wcslen(L"Bob") 返回3(本文開頭示例的出錯(cuò)原因 - 譯注)。
下一篇重我們將要講述Win32 API中的MBCS 和 Unicode。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -