C++字符串完全指引之一 —— Win32 字符編碼_第1頁
C++字符串完全指引之一 —— Win32 字符編碼_第2頁
C++字符串完全指引之一 —— Win32 字符編碼_第3頁
C++字符串完全指引之一 —— Win32 字符編碼_第4頁
C++字符串完全指引之一 —— Win32 字符編碼_第5頁
已閱讀5頁,還剩6頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、C+字符串完全指引之一 Win32 字符編碼原著:Michael Dunn翻譯:Chengjie Sun原文出處:CodeProject:The Complete Guide to C+ Strings, Part I引言毫無疑問,我們都看到過像 TCHAR, std:string, BSTR 等各種各樣的字符串類型,還有那些以 _tcs 開頭的奇怪的宏。你也許正在盯著顯示器發(fā)愁。本指引將總結引進各種字符類型的目的,展示一些簡單的用法,并告訴您在必要時,如何實現各種字符串類型之間的轉換。在第一部分,我們將介紹3種字符編碼類型。了解各種編碼模式的工作方式是很重要的事情。即使你已經知道一個字符串是

2、一個字符數組,你也應該閱讀本部分。一旦你了解了這些,你將對各種字符串類型之間的關系有一個清楚地了解。在第二部分,我們將單獨講述string類,怎樣使用它及實現他們相互之間的轉換。字符基礎 - ASCII, DBCS, Unicode所有的 string 類都是以C-style字符串為基礎的。C-style 字符串是字符數組。所以我們先介紹字符類型。這里有3種編碼模式對應3種字符類型。第一種編碼類型是單子節(jié)字符集(single-byte character set or SBCS)。在這種編碼模式下,所有的字符都只用一個字節(jié)表示。ASCII是SBCS。一個字節(jié)表示的0用來標志SBCS字符串的結束

3、。第二種編碼模式是多字節(jié)字符集(multi-byte character set or MBCS)。一個MBCS編碼包含一些一個字節(jié)長的字符,而另一些字符大于一個字節(jié)的長度。用在Windows里的MBCS包含兩種字符類型,單字節(jié)字符(single-byte characters)和雙字節(jié)字符(double-byte characters)。由于Windows里使用的多字節(jié)字符絕大部分是兩個字節(jié)長,所以MBCS常被用DBCS代替。在DBCS編碼模式中,一些特定的值被保留用來表明他們是雙字節(jié)字符的一部分。例如,在Shift-JIS編碼中(一個常用的日文編碼模式),0x81-0x9f之間和 0xe0

4、-oxfc之間的值表示"這是一個雙字節(jié)字符,下一個子節(jié)是這個字符的一部分。"這樣的值被稱作"leading bytes",他們都大于0x7f。跟隨在一個leading byte子節(jié)后面的字節(jié)被稱作"trail byte"。在DBCS中,trail byte可以是任意非0值。像SBCS一樣,DBCS字符串的結束標志也是一個單字節(jié)表示的0。第三種編碼模式是Unicode。Unicode是一種所有的字符都使用兩個字節(jié)編碼的編碼模式。Unicode字符有時也被稱作寬字符,因為它比單子節(jié)字符寬(使用了更多的存儲空間)。注意,Unicode不能被

5、看作MBCS。MBCS的獨特之處在于它的字符使用不同長度的字節(jié)編碼。Unicode字符串使用兩個字節(jié)表示的0作為它的結束標志。單字節(jié)字符包含拉丁文字母表,accented characters及ASCII標準和DOS操作系統定義的圖形字符。雙字節(jié)字符被用來表示東亞及中東的語言。Unicode被用在COM及Windows NT操作系統內部。你一定已經很熟悉單字節(jié)字符。當你使用char時,你處理的是單字節(jié)字符。雙字節(jié)字符也用char類型來進行操作(這是我們將會看到的關于雙子節(jié)字符的很多奇怪的地方之一)。Unicode字符用wchar_t來表示。Unicode字符和字符串常量用前綴L來表示。例如:w

6、char_t wch = L''1'' / 2 bytes, 0x0031wchar_t* wsz = L"Hello" / 12 bytes, 6 wide characters字符在內存中是怎樣存儲的單字節(jié)字符串:每個字符占一個字節(jié)按順序依次存儲,最后以單字節(jié)表示的0結束。例如。"Bob"的存貯形式如下:426F6200BobBOSUnicode的存儲形式,L"Bob"42 00 6F 0062 0000 00BobBOS使用兩個字節(jié)表示的0來做結束標志。一眼看上去,DBCS 字符串很像 SBCS

7、字符串,但是我們一會兒將看到 DBCS 字符串的微妙之處,它使得使用字符串操作函數和永字符指針遍歷一個字符串時會產生預料之外的結果。字符串" " ("nihongo")在內存中的存儲形式如下(LB和TB分別用來表示 leading byte 和 trail byte)93 FA96 7B8C EA00LB TBLB TBLB TBEOSEOS值得注意的是,"ni"的值不能被解釋成WORD型值0xfa93,而應該看作兩個值93和fa以這種順序被作為"ni"的編碼。使用字符串處理函數我們都已經見過C語言中的字符串函數,

8、strcpy(), sprintf(), atoll()等。這些字符串只應該用來處理單字節(jié)字符字符串。標準庫也提供了僅適用于Unicode類型字符串的函數,比如wcscpy(), swprintf(), wtol()等。微軟還在它的CRT(C runtime library)中增加了操作DBCS字符串的版本。Str*()函數都有對應名字的DBCS版本_mbs*()。如果你料到可能會遇到DBCS字符串(如果你的軟件會被安裝在使用DBCS編碼的國家,如中國,日本等,你就可能會),你應該使用_mbs*()函數,因為他們也可以處理SBCS字符串。(一個DBCS字符串也可能含有單字節(jié)字符,這就是為什么_

9、mbs*()函數也能處理SBCS字符串的原因)讓我們來看一個典型的字符串來闡明為什么需要不同版本的字符串處理函數。我們還是使用前面的Unicode字符串 L"Bob":42 00 6F 0062 0000 00BobBOS因為x86CPU是little-endian,值0x0042在內存中的存儲形式是42 00。你能看出如果這個字符串被傳給strlen()函數會出現什么問題嗎?它將先看到第一個字節(jié)42,然后是00,而00是字符串結束的標志,于是strlen()將會返回1。如果把"Bob"傳給wcslen(),將會得出更壞的結果。wcslen()將會先看到

10、0x6f42,然后是0x0062,然后一直讀到你的緩沖區(qū)的末尾,直到發(fā)現00 00結束標志或者引起了GPF。到目前為止,我們已經討論了str*()和wcs*()的用法及它們之間的區(qū)別。Str*()和_mbs*()之間的有區(qū)別區(qū)別呢?明白他們之間的區(qū)別,對于采用正確的方法來遍歷DBCS字符串是很重要的。下面,我們將先介紹字符串的遍歷,然后回到str*()與_mbs*()之間的區(qū)別這個問題上來。正確的遍歷和索引字符串因為我們中大多數人都是用著SBCS字符串成長的,所以我們在遍歷字符串時,常常使用指針的+-和-操作。我們也使用數組下標的表示形式來操作字符串中的字符。這兩種方式是用于SBCS和Unic

11、ode字符串,因為它們中的字符有著相同的寬度,編譯器能正確的返回我們需要的字符。然而,當碰到DBCS字符串時,我們必須拋棄這些習慣。這里有使用指針遍歷DBCS字符串時的兩條規(guī)則。違背了這兩條規(guī)則,你的程序就會存在DBCS有關的bugs。· 1在前向遍歷時,不要使用+操作,除非你每次都檢查lead byte; · 2永遠不要使用-操作進行后向遍歷。 我們先來闡述規(guī)則2,因為找到一個違背它的真實的實例代碼是很容易的。假設你有一個程序在你自己的目錄里保存了一個設置文件,你把安裝目錄保存在注冊表中。在運行時,你從注冊表中讀取安裝目錄,然后合成配置文件名,接著讀取該文件。假設,你的安

12、裝目錄是C:Program FilesMyCoolApp,那么你合成的文件名應該是C:Program FilesMyCoolAppconfig.bin。當你進行測試時,你發(fā)現程序運行正常?,F在,想象你合成文件名的代碼可能是這樣的:bool GetConfigFileName ( char* pszName, size_t nBuffSize ) char szConfigFilenameMAX_PATH; / Read install dir from registry. we''ll assume it succeeds. / Add on a backslash if it

13、 wasn''t present in the registry value. / First, get a pointer to the terminating zero. char* pLastChar = strchr ( szConfigFilename, ''0'' ); / Now move it back one character. pLastChar-; if ( *pLastChar != '''' ) strcat ( szConfigFilename, "" ); / A

14、dd on the name of the config file. strcat ( szConfigFilename, "config.bin" ); / If the caller''s buffer is big enough, return the filename. if ( strlen ( szConfigFilename ) >= nBuffSize ) return false; else strcpy ( pszName, szConfigFilename ); return true; 這是一段很健壯的代碼,然而在遇到 DBCS

15、 字符時它將會出錯。讓我們來看看為什么。假設一個日本用戶使用了你的程序,把它安裝在 C:。下面是這個名字在內存中的存儲形式: 433A5C83 8883 4583 5283 5C00LB TB LB TB LB TB LB TB C:EOS當使用 GetConfigFileName() 檢查尾部的''''時,它尋找安裝目錄名中最后的非0字節(jié),看它是等于''''的,所以沒有重新增加一個''''。結果是代碼返回了錯誤的文件名。哪里出錯了呢?看看上面兩個被用藍色高量顯示的字節(jié)。斜杠''&#

16、39;'的值是0x5c。'' ''的值是83 5c。上面的代碼錯誤的讀取了一個 trail byte,把它當作了一個字符。正確的后向遍歷方法是使用能夠識別DBCS字符的函數,使指針移動正確的字節(jié)數。下面是正確的代碼。(指針移動的地方用紅色標明) bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize ) char szConfigFilenameMAX_PATH; / Read install dir from registry. we''ll assume it suc

17、ceeds. / Add on a backslash if it wasn''t present in the registry value. / First, get a pointer to the terminating zero. char* pLastChar = _mbschr ( szConfigFilename, ''0'' ); / Now move it back one double-byte character. pLastChar = CharPrev ( szConfigFilename, pLastChar );

18、if ( *pLastChar != '''' ) _mbscat ( szConfigFilename, "" ); / Add on the name of the config file. _mbscat ( szConfigFilename, "config.bin" ); / If the caller''s buffer is big enough, return the filename. if ( _mbslen ( szInstallDir ) >= nBuffSize ) retu

19、rn false; else _mbscpy ( pszName, szConfigFilename ); return true; 上面的函數使用CharPrev() API使pLastChar向后移動一個字符,這個字符可能是兩個字節(jié)長。在這個版本里,if條件正常工作,因為lead byte永遠不會等于0x5c。讓我們來想象一個違背規(guī)則1的場合。例如,你可能要檢測一個用戶輸入的文件名是否多次出現了'':''。如果,你使用+操作來遍歷字符串,而不是使用CharNext(),你可能會發(fā)出不正確的錯誤警告如果恰巧有一個trail byte它的值的等于'

20、9;:''的值。與規(guī)則2相關的關于字符串索引的規(guī)則:2a. 永遠不要使用減法去得到一個字符串的索引。違背這條規(guī)則的代碼和違背規(guī)則2的代碼很相似。例如,char* pLastChar = &szConfigFilename strlen(szConfigFilename) - 1;這和向后移動一個指針是同樣的效果。回到關于str*()和_mbs*()的區(qū)別現在,我們應該很清楚為什么_mbs*()函數是必需的。Str*()函數根本不考慮DBCS字符,而_mbs*()考慮。如果,你調用strrchr("C: ", '''')

21、,返回結果可能是錯誤的,然而_mbsrchr()將會認出最后的雙字節(jié)字符,返回一個指向真的''''的指針。關于字符串函數的最后一點:str*()和_mbs*()函數認為字符串的長度都是以char來計算的。所以,如果一個字符串包含3個雙字節(jié)字符,_mbslen()將會返回6。Unicode函數返回的長度是按wchar_t來計算的。例如,wcslen(L"Bob")返回3。Win32 API中的MBCS和Unicode兩組 APIs: 盡管你也許從來沒有注意過,Win32中的每個與字符串相關的API和message都有兩個版本。一個版本接受MBC

22、S字符串,另一個接受Unicode字符串。例如,根本沒有SetWindowText()這個API,相反,有SetWindowTextA()和SetWindowTextW()。后綴A表明這是MBCS函數,后綴W表示這是Unicode版本的函數。當你 build 一個 Windows 程序,你可以選擇是用 MBCS 或者 Unicode APIs。如果,你曾經用過VC向導并且沒有改過預處理的設置,那表明你用的是MBCS版本。那么,既然沒有 SetWindowText() API,我們?yōu)槭裁纯梢允褂盟兀縲inuser.h頭文件包含了一些宏,例如: BOOL WINAPI SetWindowText

23、A ( HWND hWnd, LPCSTR lpString );BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString ); #ifdef UNICODE#define SetWindowText SetWindowTextW#else#define SetWindowText SetWindowTextA#endif 當使用MBCS APIs來build程序時,UNICODE沒有被定義,所以預處理器看到:#define SetWindowText SetWindowTextA這個宏定義把所有對SetWindowText的調用都轉換成

24、真正的API函數SetWindowTextA。(當然,你可以直接調用SetWindowTextA() 或者 SetWindowTextW(),雖然你不必那么做。)所以,如果你想把默認使用的API函數變成Unicode版的,你可以在預處理器設置中,把_MBCS從預定義的宏列表中刪除,然后添加UNICODE和_UNICODE。(你需要兩個都定義,因為不同的頭文件可能使用不同的宏。) 然而,如果你用char來定義你的字符串,你將會陷入一個尷尬的境地??紤]下面的代碼:HWND hwnd = GetSomeWindowHandle();char szNewText = "we love Bob

25、!"SetWindowText ( hwnd, szNewText );在預處理器把SetWindowText用SetWindowTextW來替換后,代碼變成:HWND hwnd = GetSomeWindowHandle();char szNewText = "we love Bob!"SetWindowTextW ( hwnd, szNewText );看到問題了嗎?我們把單字節(jié)字符串傳給了一個以Unicode字符串做參數的函數。解決這個問題的第一個方案是使用 #ifdef 來包含字符串變量的定義:HWND hwnd = GetSomeWindowHandle

26、();#ifdef UNICODEwchar_t szNewText = L"we love Bob!"#elsechar szNewText = "we love Bob!"#endifSetWindowText ( hwnd, szNewText );你可能已經感受到了這樣做將會使你多么的頭疼。完美的解決方案是使用TCHAR.使用TCHARTCHAR是一種字符串類型,它讓你在以MBCS和UNNICODE來build程序時可以使用同樣的代碼,不需要使用繁瑣的宏定義來包含你的代碼。TCHAR的定義如下:#ifdef UNICODEtypedef wcha

27、r_t TCHAR;#elsetypedef char TCHAR;#endif所以用MBCS來build時,TCHAR是char,使用UNICODE時,TCHAR是wchar_t。還有一個宏來處理定義Unicode字符串常量時所需的L前綴。#ifdef UNICODE#define _T(x) L#x#else#define _T(x) x#endif#是一個預處理操作符,它可以把兩個參數連在一起。如果你的代碼中需要字符串常量,在它前面加上_T宏。如果你使用Unicode來build,它會在字符串常量前加上L前綴。TCHAR szNewText = _T("we love Bob!

28、");像是用宏來隱藏SetWindowTextA/W的細節(jié)一樣,還有很多可以供你使用的宏來實現str*()和_mbs*()等字符串函數。例如,你可以使用_tcsrchr宏來替換strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根據你預定義的宏是_MBCS還是UNICODE來擴展成正確的函數,就像SetWindowText所作的一樣。不僅str*()函數有TCHAR宏。其他的函數如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text

29、Routine Mappings."標題下有完整的宏列表。字符串和TCHAR typedefs由于Win32 API文檔的函數列表使用函數的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR來定義的。(除了XP中引入的只適用于Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他們。type Meaning in MBCS builds Meaning in Unicode buildsWCHARwchar_twchar_tLPSTR zero-terminated string of char (cha

30、r*)zero-terminated string of char (char*)LPCSTR constant zero-terminated string of char (const char*)constant zero-terminated string of char (const char*)LPWSTRzero-terminated Unicode string (wchar_t*) zero-terminated Unicode string (wchar_t*)LPCWSTRconstant zero-terminated Unicode string (const wch

31、ar_t*)constant zero-terminated Unicode string (const wchar_t*) TCHARcharwchar_tLPTSTRzero-terminated string of TCHAR (TCHAR*) zero-terminated string of TCHAR (TCHAR*)LPCTSTR constant zero-terminated string of TCHAR (const TCHAR*)constant zero-terminated string of TCHAR (const TCHAR*)何時使用 TCHAR 和 Unicode到現在,你可能會問,我們?yōu)槭裁匆褂肬nicode。我已經用了很多年的char。下列3種情況下,使用Unicode將會使你受益:· 1你的程序只運行在Windows NT系統中。 · 2 你的程序需要處理超過MAX_PATH個字符長的文件名。 · 3 你的程序需要使用XP中引入的只有Unicode版本的API. Windows 9x 中大多數的 API 沒有實現 Unicode 版本。所以,如果你的程序要在windows 9x中運

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論