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