1 簡介
在開發過程中,我們常常會遇到ASCII、Unicode以及MBCS等不同類型的字符串,而且還需要經常進行轉換操作。本文先介紹字符的編碼方式、各種基本字符串類型,然後說明相關的幫助類,如CComBSTR、_bstr_t、CString和basic_string等,最後討論在它們之間以及和其他的數據類型的轉換方法。
2 ANSI、MBCS與UNICODE
ASCII碼(American Standard Code for Information Interchange)由一個字節中的7位(bit)表示,第8位沒有被使用,範圍是0x00 - 0x7F 共128個字符。其中32--127表示字符,32 是空格,32 以下是控制字符(不可見)。
後來擴展了ASCII的定義,ANSI(American National Standards Institute)使用的是一個字節的全部8位(bit)來表示字符,也就具有了 256個字符元;新擴展出來的字符主要是控制字符。ANSI字符集最大一個特點是向下兼容ASCII,並且留有空餘位置處理一些特殊字符。
後來,很多語系(尤其是非拉丁語,比如日文、韓文、阿拉伯文、臺灣繁體)都使用類似的方法擴展了本地字符集的定義,現在統一稱爲 MBCS 字符集(多字節字符集)。這種系統中,有些字符佔用 1 字節,有些 2 字節。這個方法是有缺陷的,因爲各個國家地區定義的字符集有交集,因此如果使用簡體中文GB-2312的軟件,就不能在臺灣繁體BIG-5的環境下運行(顯示亂碼),反之亦然。
當然,提到MBCS,就不得不提一下Code Page,即代碼頁。字符必須編碼後才能被計算機處理,那麼計算機使用的編碼方式就叫做Code Page。比如適用於簡體中文的GB2312和用於繁體中文的big5。
爲了把全世界人民所有的文字符號都統一進行編碼,於是制定了UNICODE標準字符集(寬字符集)。UNICODE 使用2個字節表示一個字符(unsigned short int、WCHAR、_wchar_t、OLECHAR),它的範圍是0x0000 - 0xFFFF 共6萬多個字符。這樣,它把在這個星球上的每一個合理的文字系統整合成了一個單一的字符集,全世界任何一個地區的軟件,可以不用修改地就能在另一個地區運行了。
在標準C++中,可以這樣定義一個MBCS或者ANSI的字符串,即:
char* pc = "zhs";
char c = 'z';
定義一個UNICODE的字符串,可以用L前綴,即:
wchar_t* pww = L"zhs";
wchar_t ww = L'z';
總結一下:
ASCII長度爲7位,共有128個字符,我們常用的英文字母和符號都包含在這裏邊。
ANSI長度爲8位,共有256個字符。前128個字符也爲ASCII。ASCII和ANSI只是在一些控制符號上有區別。
MBCS長度爲1或2個字節,不確定。
UNICODE長16位,即2個字節,被C/C++定義成wchar_t。
3 UNICODE的編碼方式
很多人還存在這樣的誤解: Unicode 僅僅是每個字符佔 2個字節,所以一共有 65536 個可能的字符。然而,這是錯誤的。
最初的Unicode編碼, 使用兩個字節表示一個字符,包括表示字符串結束符的‘0’也是兩個字節,這種編碼方式叫做UTF-16。那麼 "Hello" 表示爲:
00 48 00 65 00 6C 00 6C 00 6F 00 00
實際上,還有一種表示方式:
48 00 65 00 6C 00 6C 00 6F 00 00 00
到底高位字節在前還是低位字節在前面,是兩種不同的模式。這要看特定的 CPU 在何種模式下工作的更快。 所以這兩種都有。詳細請參考:以下是Big-Endian 和 Little-Endian 兩者概念的區別。
理論上這種解決方案很不錯。但是對於英語用戶來說,他們很少使用 00FF 以上的字符, 有些人無法忍受採用雙倍的存儲空間來存儲每個字符而造成的浪費。基於這些原因,很多人決定忽視 Unicode;而另外的人想出了別的方案。
然後人們制定了 UTF-8, UTF-8 是用於保存 Unicode的另一套編碼方案。在 UTF-8 中,任何一個 0--127 的字符佔用一個字節。只有 128 以及更大的才佔用 2, 3, 直到 6 個字節。
用UTF-8編碼的字符串,其結束符是一個字節的0;而不是UTF-16的兩個字節的0。換句話說,UTF-8編碼的字符串,會向下兼容標準C的函數。
實際上,還有一種Unicode編碼形式, 使用四個字節表示字符串中的任意一個字符,這種編碼方式叫做UTF-32,或者UCS-4。
默認情況,Windows NT操作系統的開發(尤其是VC開發),如果選擇的是UNICODE,指的就是UTF-16;如果選擇的是MBCS,則表示字符串中既有單字節字符也有雙字節字符。Intel的x86機器上都是little-endian而不是big-endian方式。
那麼在實際開發中,我們爲什麼要使用UNICODE哪?下列一些情況下,使用Unicode將會使你受益:
1.你的程序只運行在Windows NT系統中。Windows 9x 中大多數的 API 沒有實現 Unicode 版本。所以,如果你的程序要在Windows 9x中運行,你必須使用MBCS APIs。然而,由於NT系統內部都使用Unicode,所以使用Unicode APIs將會加快你的程序的運行速度。每次,你傳遞一個字符串調用MBCS API,操作系統會把這個字符串轉換成Unicode字符串,然後調用對應的Unicode API。如果一個字符串被返回,操作系統還要把它轉變回去。儘管這個轉換過程被高度優化了,但它對速度造成的損失是無法避免的。
2.你的程序需要處理超過MAX_PATH個字符長的文件名。只要你使用Unicode API,NT系統允許使用非常長的文件名(突破了MAX_PATH的限制,MAX_PATH=260)。使用Unicode API的另一個優點是你的程序會自動處理用戶輸入的各種語言。所以一個用戶可以輸入英文,中文或者日文,而你不需要額外編寫代碼去處理它們。
3.你的程序需要使用XP中引入的只有Unicode版本的API。 最後,隨着windows 9x產品的淡出,微軟似乎正在拋棄MBCS APIs。例如,包含兩個字符串參數的SetWindowTheme() API只有Unicode版本的。使用Unicode來build你的程序將會簡化字符串的處理,你不必在MBCS和Unicode之間相互轉換。
總結一下,幾種 Unicode 的表示方法:
傳統的雙字節表示方法, 稱爲 UCS-2(因爲有 2 個字節) 或者 UTF-16(因爲有 16 個位),但是你要搞清楚是高位在前的,還是高位在後的 UCS-2。
還有一種就是 UTF-8。 如果你的程序只使用英文的話,它仍然會工作正常。
另外還有 UCS-4, 儲存每一個字符爲 4 個字節。它的優點是每一個字符都保存爲同樣長的。但很明顯,缺點是浪費太多存儲空間了。
以下是Big-Endian 和 Little-Endian 兩者概念的區別,僅供參考。
Big-Endian 和 Little-Endian 字節排序
字節排序 |
含義 |
Big-Endian |
一個Word中的高位的Byte放在內存中這個Word區域的低地址處。 |
Little-Endian |
一個Word中的低位的Byte放在內存中這個Word區域的低地址處。 |
必須注意的是:表中一個Word的長度是16位,一個Byte的長度是8位。如果一個數超過一個Word的長度,必須先按Word分成若干部分,然後每一部分(即每個Word內部)按Big-Endian或者Little-Endian的不同操作來處理字節。
一個例子:
如果我們將0x1234abcd(這是一個INT32類型的數據)寫入到以0x0000開始的內存中,則結果爲:
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x34 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
(注意:0xab換算成2進制是10101011,是個8位的數。)
4 T和TCHAR
因爲我們開發面向的是具有多字節字符的中文或者日文,那麼我們可能會有MBCS和UNICODE這兩種選擇。實際開發中,我們希望把MBCS和Unicode的區別透明化,也就是說不應該到處都有這樣的代碼:
#ifdef _UNICODE
wchar_t ch;
#else
char ch;
#endif
爲了簡化開發,微軟運行時庫提供了一系列的映射,把很多字符串相關的數據類型、函數和對象給映射成了T類型,這些映射的宏都在TCHAR.H中。要決定程序中應該使用的編碼方式,可以通過VC環境中設定預編譯的宏:
Project->Settings...->C/C++ tab頁->Preprocessor definitions
對應於MBCS, Unicode,和ASCII (SBCS),分別設置爲:MBCS、UNICODE,_UNICODE 和空(什麼也不設)。
Compiled version |
Example |
|
_UNICODE |
Unicode (wide-character) |
_tcsrev maps to _wcsrev |
_MBCS |
Multibyte-character |
_tcsrev maps to _mbsrev |
None ( neither _UNICODE nor _MBCS defined) |
SBCS (ASCII) |
_tcsrev maps to strrev |
以下是一些T類型的定義在實際系統中的對應:
Generic-text |
_MBCS |
_UNICODE |
_TCHAR |
char |
wchar_t |
_tfinddata_t |
_finddata_t |
_wfinddata_t |
_tfinddata64_t |
__finddata64_t |
__wfinddata64_t |
_tfinddatai64_t |
_finddatai64_t |
_wfinddatai64_t |
_TSCHAR |
signed char |
wchar_t |
_TUCHAR |
unsigned char |
wchar_t |
_TXCHAR |
unsigned char |
wchar_t |
_T or _TEXT |
No effect (removed by preprocessor) |
L(converts following character or string to its Unicode counterpart) |
對於字符串的數據類型,我們可以使用TCHAR:
#ifdef _UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif
所以,TCHAR中在MBCS程序中是char類型,在Unicode中是 wchar_t 類型。
對於字符串常量,有個 _T() 宏,用於解決 L 前綴:
#ifdef _UNICODE
#define _T(x) L##x
#else
#define _T(x) x
#endif
## 是預處理算子,將二個變量粘貼在一起。宏_T有幾種形式,功能都相同。如: -- TEXT, _TEXT, __TEXT, 和 __T這四種宏的功能相同。
不管什麼時候都應該對字符串用 _T 宏處理,這樣就可以在Unicode編碼中給字符串加上L前綴,而對ANSI則沒有任何影響:
TCHAR szNewText[] = _T("we love Bob!");
另外,Microsoft還擴展了一些常用的字符和字符串類型,其中,P表示point,C表示const,T表示TCHAR,W表示wide。如下表所示:
type |
Meaning in MBCS builds |
Meaning in Unicode builds |
WCHAR |
wchar_t |
wchar_t |
LPSTR |
zero-terminated string of char (char*) |
zero-terminated string of char (char*) |
LPCSTR |
constant zero-terminated string of char (const char*) |
constant zero-terminated string of char (const char*) |
LPWSTR |
zero-terminated Unicode string (wchar_t*) |
zero-terminated Unicode string (wchar_t*) |
LPCWSTR |
constant zero-terminated Unicode string (const wchar_t*) |
constant zero-terminated Unicode string (const wchar_t*) |
TCHAR |
char |
wchar_t |
LPTSTR |
zero-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*) |
注意,這些映射的定義是微軟擴展的,並非ANSI標準,所以它是不可移植的。
5 C風格的字符串,即:字符數組
所有的字符串類都起源於C風格的字符串。
C風格字符串是字符的數組,數組中每一個元素保存了一個字符,可以是單字節或者多字節,最後以零字節表示字符串結尾;對於單字節或者MBCS字符串,用一個字節0x00結束字符串,對於Unicode,則用兩個字節0x0000結束字符串。
C語言字符串處理函數,如strcpy(), sprintf(), atol()等只能用於單字節字符串。在標準庫中有隻用於Unicode字符串的函數,如wcscpy(), swprintf(), _wtol()。微軟在C運行庫(CRT)中加入了對MBCS字符串的支持,使用_mbsxxx()函數,所以在處理DBCS字符串(如日語,中文,或其它MBCS)時,就要用_mbsxxx()函數。
現在用一個示例來說明字符串處理函數的不同。如有Unicode字符串L"Bob":
對於strxxx() 和 _mbsxxx() 函數族中的字符串長度測量函數,它們都返回字符串的字符數。如果字符串含有3個雙字節字符,_mbslen()將返回3。Unicode的函數返回的是wchar_t的數量,如wcslen(L"Bob") 返回3。
這時如用strlen()函數求字符串的長度就發生問題。函數找到第一個字節42,然後是00,意味着字符串結尾,於是返回1。而如果用Unicode的函數,wcslen(L"Bob"),則返回3,因爲他判斷的是雙字節的00。
我們中的大多數人都是從單字節字符集成長過來的,都習慣於用指針的 ++ 和 -- 操作符來遍歷字符串,有時也使用數組來處理字符串中的字符。這二種方法對於純粹的單字節字符串和 Unicode 字符串的操作都是正確無誤的,因爲二者的字符都是等長的,編譯器能夠的正確返回我們尋求的字符位置。
但對於MBCS字符串就不能這樣了,因爲每一個字符的長度不確定。所以,用指針訪問MBCS字符串不可使用 ++ 算子,除非每次都檢查是否爲前導字節,絕不可使用 -- 算子來向後遍歷。正確的方法是用MBCS函數將指針指向恰當的字符位置,比如CharPrev()和CharNext()函數,它會根據情況而決定移動一個或者兩個字節。
6 STL的string、wstring和其他
STL只有一個字符串類,即basic_string模板類,basic_string管理一個零結尾的字符數組,字符類型(char或者wchar_t)由模板參數決定。
basic_string預定義了二個特例:string,含有char類型字符;wstring,含有wchar_t類型字符。這兩個類型是如下定義的:
typedef basic_string<wchar_t> wstring;
typedef basic_string<char> string;
沒有內建的TCHAR特例,可用下面的代碼自己實現:
typedef basic_string<TCHAR> tstring;
basic_string模板類提供了和CString一樣的功能,而且還有其他的優勢:比CString(是MFC的)的效率高;它是標準C++庫的內容,易於移植。
如何使用string和wstring。
首先需要包含頭文件<string>,然後引用名字空間using namespace std;之後就像普通的類一樣使用。比如,要使用replace功能,可以如下使用:
#include <string>
{
using namespace std;
string result1a, result1b;
string s1o ( "AAAAAAAA" );
string s1p ( "BBB" );
result1a = s1o.replace ( 1 , 3 , s1p );
}
關於詳細介紹,請參看另外的一篇文檔< STLString.doc >。
關於詳細介紹,還可以參看另外的一篇文檔< The C++ Programming Language, by Bjarne Stroustrup >,第20章”Stgrings”。
7 MFC中的CString及其他
CString是MFC庫提供的類,它保存並管理一個TCHAR數組,它的實際字符類型取決於預處理標記的設置。
CString比STL字符串優越的是它的構造函數接受MBCS和Unicode字符串,並且可以轉換爲LPCTSTR,因此可以向接受LPCTSTR的函數直接傳遞CString對象,不必像std::string那樣調用c_str()方法;另外,它還可以從字符資源表中加在字符串來進行構造。
CString的使用,要比string簡單,因爲如果我們創建了一個VC工程,默認的就已經包含了CString的類,而且也不需要引用名字空間。
// 構造
CString s1 = "char string"; // 從LPCSTR構造
CString s2 = L"wide char string"; // 從LPCWSTR構造
CString s3 ( ' ', 100 ); // 預分配100字節,填充空格
CString s4 = "New window text";
CString s6, s7;
s6.LoadString ( IDS_SOME_STR ); // 從字符串表加載
s7.Format ( IDS_SOME_FORMAT, "bob", nSomeStuff, ... ); // 從字符串表加載打印格式的字符串
//顯示或者隱式的類型轉化
SetWindowText ( hwndSomeWindow, s4 ); // 隱式轉化成LPCTSTR
SetWindowText ( hwndSomeWindow, (LPCTSTR) s4 ); // 或者,顯式地做強制類型轉換
注意,CString只允許一種強制類型轉換,即強制轉換爲LPCTSTR。強制轉換爲LPTSTR (非常量指針)是錯誤的。按照老習慣,將CString強制轉換爲LPTSTR只能傷害自己。有時在程序中沒有發現出錯,那只是碰巧。轉換到非常量指針的正確方法是調用GetBuffer()方法,這種方法是把CString中的字符串指針拿出來,如果用戶修改這段內容,CString中的內容也相應地被修改了,這種方法在沒有強烈需求的情況下也儘量不使用。進行非常量的強制類型轉換,打破了面向對象的封裝原則,並逾越了CString的內部操作,容易出Bug。
關於詳細介紹,請參看另外的一篇文檔< CStringClass.doc >。
8 COM中的BSTR、CComBSTR、_bstr_t和其他
COM中對字符串有特殊的要求,即:寬字符,告知對方字符串的長度。
OLECHAR在win32中爲16位,即2個字節,被C/C++定義成wchar_t。
OLECHAR在win16中爲8位,即1個字節,被C/C++定義成char。
當然,我們的開發都是基於Win32的,所以我們在開發過程中會認爲OLECHAR就是被定義成了wchar_t。
8.1BSTR
BSTR是一個指向UNICODE 字符串的指針,這個字符串可以沒有結束符,在BSTR向前的4個字節中,使用DWORD保存着這個字符串的字節長度。也就是說,它的長度不是由NULL結尾字符決定,而是由長度前綴決定,雖然BSTR也可能有NULL字符在內部或者結尾處。它主要用在COM以及Automation方面。
看看定義就知道了:
typedef wchar_t WCHAR;
typedef WCHAR OLECHAR;
typedef OLECHAR* BSTR;
注意:BSTR不等於OLECHAR*,最大的區別在於它有一個長度前綴。
通俗地說,你不能直接把一個內存指針直接作爲參數傳遞給COM函數。系統需要把這塊內存的內容傳遞到“地球另一邊”的計算機上,因此,系統至少需要知道你這塊內存的尺寸,也就是說,需要傳遞多少字節給用戶。而字符串又是非常常用的一種類型,因此 COM 設計者引入了BSTR。因此係統就能夠正確處理並傳送這個字符串到“地球另一 邊”了。特別需要注意的是,由於BSTR的指針就是指向 UNICODE 串,因此 BSTR 和 LPOLESTR 可以在一定程度上混用,但是不建議這樣做。
關於BSTR的詳細介紹,請參看另外的一篇文檔< BSTR.doc >。
8.2_bstr_t
_bstr_t 是C++運行時庫提供的對BSTR的完全包裝類。實際上,它隱含了BSTR的大多數細節,它提供多種構造函數,能夠處理隱含的C類型字符串以及BSTR字符串。它雖然提供了BSTR的處理機制,但是不能作爲COM方法的輸出參數[out],因爲他的操作符重載只有wchar_t而沒有BSTR;他可以作爲輸入傳遞給需要BSTR數據的函數,但是,不建議這樣做。如果要用到BSTR* 類型數據,用ATL的CComBSTR類更爲方便。
_bstr_t使用示例如下:
// 構造
_bstr_t bs1 = _T("wide char string"); // 從LPCWSTR構造
_bstr_t bs3 = bs1; // 拷貝另一個 _bstr_t
// 數據萃取
LPCTSTR pwsz1 = bs1; // 返回內部的Unicode字符串
BSTR bstr = bs1.copy(); // 拷貝bs1, 返回BSTR
SysFreeString ( bstr );
8.3CComBSTR
CComBSTR 是ATL的BSTR包裝類,某些情況下比_bstr_t 更有用。最主要的是,CComBSTR允許操作隱含BSTR,就是說,傳遞一個CComBSTR對象給COM方法時,CComBSTR對象會自動管理BSTR內存。例如,要調用下面的接口函數:
// 簡單接口
struct IStuff : public IUnknown
{
STDMETHOD(SetText)(BSTR bsText);
STDMETHOD(GetText)(BSTR* pbsText);
};
CComBSTR 有一個BSTR操作方法,能將BSTR直接傳遞給SetText()。還有一個引用操作(operator &)方法,返回BSTR*,將BSTR*傳遞給需要它的有關函數。
CComBSTR bs1;
CComBSTR bs2 = _T("new text");
pStuff->GetText ( &bs1 ); // ok, 取得內部BSTR地址
pStuff->SetText ( bs2 ); // ok, 調用BSTR轉換
pStuff->SetText ( (BSTR) bs2 ); // cast ok, 同上
CComBSTR有類似於 _bstr_t 的構造函數。但沒有內建MBCS字符串的轉換函數。可以調用ATL宏進行轉換。
// 構造
CComBSTR bs3 = bs1; // 拷貝CComBSTR
CComBSTR bs4;
bs4.LoadString ( IDS_SOME_STR ); // 從字符串表加載
// 數據萃取
BSTR bstr1 = bs1; // 返回內部BSTR,但不可修改!
BSTR bstr2 = (BSTR) bs1; // cast ok, 同上
BSTR bstr3 = bs1.Copy(); // 拷貝bs1, 返回BSTR
BSTR bstr4;
bstr4 = bs1.Detach(); // bs1不再管理它的BSTR
// ...
SysFreeString ( bstr3 );
SysFreeString ( bstr4 );
上面的最後一個示例用到了Detach()方法。該方法調用後,CComBSTR對象就不再管理它的BSTR或其相應內存。所以bstr4就必須調用SysFreeString()。
最後討論一下引用操作符(operator &)。它的超越使得有些STL集合(如list)不能直接使用CComBSTR。在集合上使用引用操作返回指向包容類的指針。但是在CComBSTR上使用引用操作,返回的是BSTR*,不是CComBSTR*。不過可以用ATL的CAdapt類來解決這個問題。例如,要建立一個CComBSTR的隊列,可以聲明爲:
std::list< CAdapt<CComBSTR>> bstr_list;
CAdapt 提供集合所需的操作,是隱含於代碼的。這時使用bstr_list 就象在操作一個CComBSTR隊列。
關於詳細介紹,請參看另外的一篇文檔< CCOMBSTR.doc >,目前還沒有寫完。
9 字符串的處理函數以及常用算法
首先介紹一些通用函數命名規則:
字符集 |
特性 |
實例 |
ANSI |
操作函數以str開頭 |
strcpy |
Unicode |
操作函數以wcs開頭 |
wcscpy |
MBCS |
操作函數以_mbs開頭 |
_mbscpy |
ANSI/Unicode |
操作函數以_tcs開頭(C運行期庫) |
_tcscpy |
ANSI/Unicode |
操作函數以lstr開頭(Windows函數) |
lstrcpy |
下表列出了一些運行時庫的字符串處理API(比較常用的用淺藍色標出)。這些API是針對C風格的字符串的處理函數,如果你用到了CComBSTR、_bstr_t、CString等,就使用這些類提供的成員函數就好了,各組函數之間在功能上基本上沒有大的差別,只是實現方式的一些差異。
Routine |
Use |
Move string pointer back one character |
|
Advance string pointer by one character |
|
Find next character in string |
|
Advance string pointer by n characters |
|
Return pointer to first character in given string that is not in another given string |
|
Return the number of characters in a formatted string |
|
Read formatted data of a specified length from the standard input stream. |
|
Write formatted data to a string |
|
Append one string to another |
|
Find first occurrence of specified character in string |
|
Compare two strings |
|
strcoll, wcscoll, _stricoll, _wcsicoll, _strncoll, _wcsncoll, _strnicoll, _wcsnicoll |
Compare two strings using current locale code page information (_stricoll, _wcsicoll, _strnicoll, and _wcsnicoll are case-insensitive) |
Copy one string to another |
|
Find first occurrence of character from specified character set in string |
|
Duplicate string |
|
Map error number to message string |
|
Map user-defined error message to string |
|
Format date-and-time string |
|
Compare two strings without regard to case |
|
Find length of string |
|
Convert string to lowercase |
|
Append characters of string |
|
Compare characters of two strings |
|
Copy characters of one string to another |
|
Compare characters of two strings without regard to case |
|
Set first n characters of string to specified character |
|
Find first occurrence of character from one string in another string |
|
Find last occurrence of given character in string |
|
Reverse string |
|
Set all characters of string to specified character |
|
Find first substring from one string in another string |
|
Find first occurrence of specified string in another string |
|
Find next token in string |
|
Convert string to uppercase |
|
Transform string into collated form based on locale-specific information |
|
Write formatted output using a pointer to a list of arguments |
如果使用的是BSTR,那麼如下的一些API需要用到:
String Manipulation Functions |
Descriptions |
Creates and initializes a string. |
|
Creates a zero-terminated string of a specified length (32-bit only). |
|
Creates a string of a specified length. |
|
Frees a previously created string. |
|
Changes the size and value of a string. |
|
Changes the size of an existing string. |
|
Returns the length of a string in bytes (32-bit only). |
|
Returns the length of a string. |
|
CString::AllocSysString() |
從 CString 得到 BSTR |
CString::SetSysString() |
重新申請 BSTR 指針,並復制到 CString 中 |
如果用到的是STL(標準庫)中的string或者wstring,那麼除了可以使用自己模板類的函數之外,還有一些STL中的強大功能供你使用,這些算法無論從通用性還是效率上,都異常強大。
下表列舉算法庫中的一些函數,注意,在使用之前需要包含<algorithm>。
Tests whether there is an element in a sorted range that is equal to a specified value or that is equivalent to it in a sense specified by a binary predicate. |
|
Assigns the values of elements from a source range to a destination range, iterating through the source sequence of elements and assigning them new positions in a forward direction. |
|
Returns the number of elements in a range whose values match a specified value. |
|
Returns the number of elements in a range whose values match a specified condition. |
|
Compares two ranges element by element either for equality or equivalence in a sense specified by a binary predicate. |
|
Locates the position of the first occurrence of an element in a range that has a specified value. |
|
Searches for the first occurrence of any of several values within a target range or for the first occurrence of any of several elements that are equivalent in a sense specified by a binary predicate to a specified set of the elements. |
|
Locates the position of the first occurrence of an element in a range that satisfies a specified condition. |
|
Applies a specified function object to each element in a forward order within a range and returns the function object. |
|
Tests whether one sorted range contains all the elements contained in a second sorted range, where the ordering or equivalence criterion between elements may be specified by a binary predicate. |
|
Compares two objects and returns the larger of the two, where the ordering criterion may be specified by a binary predicate. |
|
Combines all the elements from two sorted source ranges into a single, sorted destination range, where the ordering criterion may be specified by a binary predicate. |
|
Compares two objects and returns the lesser of the two, where the ordering criterion may be specified by a binary predicate. |
|
Arranges a specified number of the smaller elements in a range into a nondescending order or according to an ordering criterion specified by a binary predicate. |
|
Eliminates a specified value from a given range without disturbing the order of the remaining elements and returning the end of a new range free of the specified value. |
|
Copies elements from a source range to a destination range, except that elements of a specified value are not copied, without disturbing the order of the remaining elements and returning the end of a new destination range. |
|
Examines each element in a range and replaces it if it matches a specified value. |
|
Reverses the order of the elements within a range. |
|
Reverses the order of the elements within a source range while copying them into a destination range |
|
Searches for the first occurrence of a sequence within a target range whose elements are equal to those in a given sequence of elements or whose elements are equivalent in a sense specified by a binary predicate to the elements in the given sequence. |
|
Searches for the first subsequence in a range that of a specified number of elements having a particular value or a relation to that value as specified by a binary predicate. |
|
Arranges the elements in a specified range into a nondescending order or according to an ordering criterion specified by a binary predicate. |
|
Arranges the elements in a specified range into a nondescending order or according to an ordering criterion specified by a binary predicate and preserves the relative ordering of equivalent elements. |
|
Exchanges the values of the elements between two types of objects, assigning the contents of the first object to the second object and the contents of the second to the first. |
|
Applies a specified function object to each element in a source range or to a pair of elements from two source ranges and copies the return values of the function object into a destination range. |
|
Removes duplicate elements that are adjacent to each other in a specified range. |
10 各種類型的字符串之間轉換
常用的字符串類之間的轉換方法是:將源字符串轉換爲C類型字符串指針,然後將該指針傳遞給目標類的構造函數。
10.1 TCHAR*轉換成CString
若將TCHAR*轉換成CString,除了直接賦值外,還可使用CString::Format進行。例如:
TCHAR chArray[] = _T("This is a test"); |
10.2 CString轉換成TCHAR*
方法一,使用強制轉換。例如:
CString theString(_T("This is a test") ); |
方法二,使用strcpy。例如:
CString theString(_T("This is a test") ); |
方法三,使用CString::GetBuffer。例如:
CString s(_T("This is a test ")); // 使用完後及時釋放,以便能使用其它的CString成員函數 |
10.3 TCHAR*轉換成BSTR
方法一,使用SysAllocString等API函數。例如:
BSTR bstrText = ::SysAllocString(_T("Test")); |
方法二,使用_bstr_t,這是一種最簡單的方法。例如:
BSTR bstrText = _bstr_t(_T("This is a test")); |
方法三,使用CComBSTR。例如:
BSTR bstrText = CComBSTR(_T("This is a test")); |
或
CComBSTR bstr(_T("This is a test")); |
方法四,使用ConvertStringToBSTR。例如:
TCHAR* lpszText = _T("Test"); |
10.4 BSTR轉換成TCHAR*
方法一,使用ConvertBSTRToString。例如:
BSTR bstrText = ::SysAllocString(_T("Test")); // 用完釋放 |
方法二,使用_bstr_t的賦值運算符重載。例如:
_bstr_t b = bstrText; |
10.5 CString轉換成BSTR
CString的AllocSysString()和SetSysString()能夠從CString中得到BSTR,並在必要時轉換成Unicode。除了SetSysString()使用BSTR*參數外,二者一樣。他們內部調用了SysAllocString,所以由他們創建的BSTR,需要使用者顯式的釋放SysFreeString。
例如:
CString s5 = _T("Bob!"); BSTR bs1 = NULL, bs2 = NULL; bs1 = s5.AllocSysString(); s5.SetSysString ( &bs2 ); // ... SysFreeString ( bs1 ); // 用完釋放 SysFreeString ( bs2 ); |
10.6 BSTR轉換成CString
一般可按下列方法進行:
BSTR bstrText = ::SysAllocString(_T("Test")); |
或
CStringA str(bstrText); |
10.7 basic_string轉化成TCHAR*
使用basic_string::c_str()方法轉化成C風格的字符串。
10.8 ANSI、Unicode和寬字符之間的轉換
方法一,使用MultiByteToWideChar將MBCS字符轉換成Unicode字符,使用WideCharToMultiByte將Unicode字符轉換成ANSI字符。
方法二,使用“_T”將ANSI轉換成“一般”類型字符串,使用“L”將ANSI轉換成Unicode。例如:
TCHAR tstr[] = _T("this is a test"); |
方法三,使用ATL 3.0(即VC6.0的ATL)的轉換宏和類。
ATL的字符串轉換宏可以方便地轉換不同編碼的字符,用在函數中很有效。宏按照[source type]2[new type] 或 [source type]2C[new type]格式命名。後者轉換爲一個常量指針 (名字內含"C")。
ATL的轉換宏:
A2BSTR |
OLE2A |
T2A |
W2A |
A2COLE |
OLE2BSTR |
T2BSTR |
W2BSTR |
A2CT |
OLE2CA |
T2CA |
W2CA |
A2CW |
OLE2CT |
T2COLE |
W2COLE |
A2OLE |
OLE2CW |
T2CW |
W2CT |
A2T |
OLE2T |
T2OLE |
W2OLE |
A2W |
OLE2W |
T2W |
W2T |
上表中的宏函數縮寫含義如下:
2 |
表示“轉換爲、轉換到”的含義。 |
A |
MBCS字符串,char* (A for ANSI) |
W、OLE |
寬字符串。也就是 UNICODE,wchar_t* (W for wide);OLECHAR字符串OLECHAR* (實際等於W)。 |
T |
TCHAR字符串,TCHAR*。如果定義了_UNICODE,則T表示W;如果定義了 _MBCS,則T表示A |
C |
const 的縮寫 |
BSTR |
BSTR (只用於目的類型) |
例如,W2A() 將Unicode字符串轉換爲MBCS字符串,T2CW()將TCHAR字符串轉換爲Unicode字符串常量。
要使用宏轉換,程序中要包含atlconv.h頭文件。可以在非ATL程序中使用宏轉換,因爲頭文件不依賴其它的ATL,也不需要 _Module全局變量。如在函數中使用轉換宏,在函數起始處先寫上USES_CONVERSION宏,它表明某些局部變量由宏控制使用。
轉換得到的結果字符串,只要不是BSTR,都存儲在堆棧中。如果要在函數外使用這些字符串,就要將這些字符串拷貝到其它的字符串類。如果結果是BSTR,內存不會自動釋放,因此必須將返回值分配給一個BSTR變量或BSTR的包裝類,以避免內存泄露,或者自己處理BSTR的內存。
示例:
// 帶有字符串的函數:
void Foo ( LPCWSTR wstr );
void Bar ( BSTR bstr );
// 返回字符串的函數:
void Baz ( BSTR* pbstr );
#include <atlconv.h>
{
using std::string;
USES_CONVERSION; // 聲明局部變量由宏控制使用
// 示例1:送一個MBCS字符串到Foo()
LPCSTR psz1 = "Bob";
string str1 = "Bob";
Foo ( A2CW(psz1) );
Foo ( A2CW(str1.c_str()) );
// 示例2:將MBCS字符串和Unicode字符串送到Bar()
LPCSTR psz2 = "Bob";
LPCWSTR wsz = L"Bob";
BSTR bs1;
CComBSTR bs2;
bs1 = A2BSTR(psz2); // 創建 BSTR
bs2.Attach ( W2BSTR(wsz) ); // 同上,分配到CComBSTR
Bar ( bs1 );
Bar ( bs2 );
SysFreeString ( bs1 ); // 釋放bs1
// 不必釋放bs2,由CComBSTR釋放。
// 示例3:轉換由Baz()返回的BSTR
BSTR bs3 = NULL;
string str2;
Baz ( &bs3 ); // Baz() 填充bs3內容
str2 = W2CA(bs3); // 轉換爲MBCS字符串
SysFreeString ( bs3 ); // 釋放bs3
}
10.9 寬字符和MBCS的轉化
這裏所說的MBCS,實際上是指一個字符串中既含有單字節還有雙字節的字符。一般來說,使用WideCharToMultiByte()和MultiByteToWideChar()來進行相互的轉化。
1. 函數WideCharToMultiByte (),轉換UNICODE到MBCS。使用範例:
LPCOLESTR lpw = L"Hello,你好";
size_t wLen = wcslen( lpw ) + 1; // 寬字符字符長度,+1表示包含字符串結束符
int aLen=WideCharToMultiByte( //計算所需 MBCS 字符串字節長度
CP_ACP,
0,
lpw, // 寬字符串指針
wLen, // 字符長度
NULL,
0, // 參數0表示計算轉換後的字符空間
NULL,
NULL);
LPSTR lpa = new char [aLen];
WideCharToMultiByte( //進行轉化
CP_ACP,
0,
lpw,
wLen,
lpa, // 轉換後的字符串指針
aLen, // 給出空間大小
NULL,
NULL);
// 此時,lpa 中保存着轉換後的 MBCS 字符串
... ... ... ...
delete [] lpa;
2、函數 MultiByteToWideChar(),轉換 MBCS 到 UNICODE。使用範例:
LPCSTR lpa = "Hello,你好";
size_t aLen = strlen( lpa ) + 1;
int wLen = MultiByteToWideChar(
CP_ACP,
0,
lpa,
aLen,
NULL,
0);
LPOLESTR lpw = new WCHAR [wLen];
MultiByteToWideChar(
CP_ACP,
0,
lpa,
aLen,
lpw,
wLen);
... ... ... ...
delete [] lpw;
關於這兩個函數的第一個參數,即CP_ACP,就是要指定Code Page,可選的Code Page,比如:
Value |
Meaning |
CP_ACP |
ANSI code page |
CP_MACCP |
Macintosh code page |
CP_OEMCP |
OEM code page |
CP_SYMBOL |
Windows 2000/XP: Symbol code page (42) |
CP_THREAD_ACP |
Windows 2000/XP: The current thread's ANSI code page |
CP_UTF7 |
Windows 98/Me, Windows NT 4.0 and later: Translate using UTF-7 |
CP_UTF8 |
Windows 98/Me, Windows NT 4.0 and later: Translate using UTF-8. |
其他的Code Page,在使用的時候情查詢MSDN的Code-Page Identifiers以獲取更多和更詳細的信息。
11 String和其他數據類型(數字類型)的轉化
這裏所說的其他類型主要是數字類型。
下表列出了一些運行時庫的字符串轉化處理API:
Routine |
Use |
Convert double to string of specified length |
|
Convert double to string with specified number of digits following decimal point |
|
Convert double number to string; store string in buffer |
|
Convert int to string |
|
Convert long to string |
|
Convert string to double |
|
Convert string to long integer |
|
Convert string to unsigned long integer |
|
Convert string to a double |
|
Convert string to int or __int64 |
|
Convert string to long |
當然把其他類型轉化成字符串,還有printf()和wprintf()函數來使用。
有一些字符串類,如CComBSTR或者_bstr_t等,本身不提供和其他數據類型相互轉化的功能(比如,format等),所以只能先把字符串數據取出來,轉化成C風格的字符串,然後調用運行時庫的API來進行轉化;或者先把其他類型數據轉化成字符串類型,然後通過API來轉化。
12 一些建議
在Win32平臺上開發,有一些共同的原則要遵循。
1. UNICODE方式編譯
應該設置爲_UNICODE方式編譯。設置條件編譯的方式是:VC6中,Project->Settings...->C/C++ tab頁->Preprocessor definitions中把_MBCS修改爲_UNICODE,UNICODE。同時,在ProjectSetting/link/output 中設置Entry爲wWinMainCRTStartup。
注意:_UNICODE和UNICODE是不同的。_UNICODE宏用於C運行期頭文件,而UNICODE宏則用於Windows頭文件。但是一般我們並不區分這種區別,而是籠統的把這兩個宏都定義上。
如果用的是UNICODE字符,調試中經常發現只能看到第一個字符的問題,可以作如下設置就可以看到全部的字符串了:
Tools->Options...->Debug tab頁->Display Unicode strings
打上勾就好了。
2. 使用T類型
在實際編碼中,必須使用T類型,這是非常好的習慣,必須遵守。
//1.將CString >> TCHAR
CString str1 = TEXT("將CString >> TCHAR");
TCHAR sz1[64] = {0};
_tcscpy(sz1,str1);
AfxMessageBox(sz1);
//2.將CString >> LPTSTR(有三種方法)
CString str2_1 = TEXT("將CString >> LPTSTR 第一種");
LPTSTR lpsz2_1 = (LPTSTR)(LPCTSTR)str2_1;
AfxMessageBox(lpsz2_1);
//
CString str2_2 = TEXT("將CString >> LPTSTR 第二種");
LPTSTR lpsz2_2 = new TCHAR[str2_2.GetLength()+3];
_tcscpy(lpsz2_2,str2_2);
AfxMessageBox(lpsz2_2);
delete[] lpsz2_2;//用完後,釋放內存
AfxMessageBox(lpsz2_2);
//
CString str2_3 = TEXT("將CString >> LPTSTR 第三種");
LPTSTR lpsz2_3 = str2_3.GetBuffer(str2_3.GetLength()+3/*buffer最小長度*/);
str2_3.ReleaseBuffer();
AfxMessageBox(lpsz2_3);
//3.將TCHAR >> CString
TCHAR sz3[64] = TEXT("將TCHAR >> CString");
CString str3;
str3 = sz3;
AfxMessageBox(str3);
//4.將LPTSTR >> CString
LPTSTR lpsz4 = TEXT("將LPTSTR >> CString");
CString str4 = lpsz4;
AfxMessageBox(str4);
//5.將TCHAR >> LPTSTR
TCHAR sz5_1[64] = TEXT("將TCHAR >> LPTSTR 第一種 555");
LPTSTR lpsz5_1 = sz5_1;
AfxMessageBox(lpsz5_1);
//
TCHAR sz5_2[64] = TEXT("將TCHAR >> LPTSTR 第二種 555");
LPTSTR lpsz5_2 = new TCHAR[lstrlen(sz5_2)+3];
_tcscpy(lpsz5_2,sz5_2);
AfxMessageBox(lpsz5_2);
delete[] lpsz5_2;
AfxMessageBox(lpsz5_2);
//6.將LPTSTR >> TCHAR
LPTSTR lpsz6 = TEXT("將LPTSTR >> TCHAR 666");
TCHAR sz6[64] = {0};
_tcscpy(sz6,lpsz6);
AfxMessageBox(sz6);
//結論:
//1.把CString看成LPTSTR,如str.GetBuffer(str.GetLength()+3)就是一個指針,而GetBuffer的參數爲指針長度.
//2.當給LPTSTR賦值時,只能用=,如 LPTSTR sz = str(上面結論裏得到CString是指針,指針可以給值給指針),
//也可以用_tcscpy,因_tcscpy是內存操作,所以要給LPTSTR分配內存指針長度,如LPTSTR lpsz = new TCHAR[lstrlen(sz)+3].
//3.當給TCHAR賦值時,只能用_tcscpy(sz,X);
//4.當TCHAR爲賦值者時(被給值不爲TCHAR),如 LPTSTR lpsz = sz 或者 CString str = sz,這裏sz被看成是字符指針.
//約定
//sz 表示 TCHAR
//lpsz 表示 LPTSTR
//lpcsz 表示 LPCTSTR
//str 表示 CString