
2.10 字符串
字符串相信大家很熟悉了,无论在C语言中还是C++中,都提供了丰富的字符串函数来操作字符串。本小节介绍通过Win32 API函数来处理字符串,以及通过MFC中的CString类来处理字符串。
2.10.1 几个字符串类型
Visual C++编程会碰到MBCS和UNICODE两种编码的字符串,具体采用哪种编码要看工程属性中设置的字符集,而Visual C++中的字符串类型有这几种:LPSTR、LPCSTR 、LPTSTR、LPCTSTR。L表示long指针,这是为了兼容Windows 3.1等16位操作系统遗留下来的,在Win32以及其他的32位操作系统中,long指针和near指针及far修饰符都是为了兼容的作用,没有实际意义;P表示这是一个指针;C表示是一个常量;T表示一个宏_T,这个宏用来表示你的字符是否使用UNICODE编码,如果工程属性的字符集选择了UNICODE编码,那么这个字符或者字符串将被作为UNICODE字符串,否则就是标准的MBCS字符串;STR表示这个变量是一个字符串。
大家知道C++的字符分成两种类型wchar_t和char,前者定义的字符占两个字节空间(在Windows下),后者定义的字符占一个字节。Visual C++又定义了等价的字符类型WCHAR和CHAR,它们定义如下:
typedef char CHAR; typedef wchar_t WCHAR; // 16-bit UNICODE character
LPSTR的定义如下:
typedef CHAR * LPSTR;
LPCSTR的定义如下:
typedef CONST CHAR * LPCSTR
因此LPSTR和LPCSTR都是指针,指向char*的,只不过后者是指向CONST CHAR*。LPSTR可以这样使用:
LPSTR lpstrMsg = "hello world"; char strMsg[]="hello world "; LPSTR lpstrMsg = (LPSTR) strMsg;
LPTSTR定义是这样的:
#ifdef UNICODE typedef LPWSTR LPTSTR; #else typedef LPSTR LPTSTR #endif
其中,LPWSTR就是WCHAR*,定义如下:
typedef WCHAR * LPWSTR;
LPCTSTR的定义如下:
#ifdef UNICODE typedef LPCWSTR LPCTSTR; #else typedef LPCSTR LPCTSTR; #endif
其中,LPCWSTR就是CONST WCHAR*,定义如下:
typedef CONST WCHAR * LPCWSTR;
我们来看一下单字节字符串char*/string和宽字节字符串wchar_t*/wstring之间的相互转换。代码如下:
(1)将单字节char*转换为宽字节wchar_t*
wchar_t* cs2wcs( const char* sz ) { size_t len = strlen( sz ) + 1; size_t converted = 0; wchar_t* wsz = (wchar_t*)malloc( len*sizeof(wchar_t) ); mbstowcs_s(&converted, wsz, len, sz, _TRUNCATE); return wsz; }
(2)将宽字节wchar_t*转换单字节char*
char* wcs2cs( const wchar_t* wsz ) { size_t len = wcslen(wsz) + 1; size_t converted = 0; char* sz = (char*)malloc(len*sizeof(char)); wcstombs_s(&converted, sz, len, wsz, _TRUNCATE); return sz; }
以上代码在MFC对话框工程中可以运行通过,但要注意的是函数wcstombs不支持中文。因此该方法不完美,更好的方法如下:
wstring MultCHarToWideChar(string str) { //获取缓冲区的大小,并申请空间,缓冲区大小是按字符计算的 int len=MultiByteToWideChar(CP_ACP,0, str.c_str(), str.size(), NULL,0); TCHAR *buffer=new TCHAR[len+1]; //多字节编码转换成宽字节编码 MultiByteToWideChar(CP_ACP,0, str.c_str(), str.size(), buffer, len); buffer[len]='\0'; //添加字符串结尾 //删除缓冲区并返回值 wstring return_value; return_value.append(buffer); delete []buffer; return return_value; } string WideCharToMultiChar(wstring str) { string return_value; //获取缓冲区的大小,并申请空间,缓冲区大小是按字节计算的 int len=WideCharToMultiByte(CP_ACP,0, str.c_str(), str.size(), NULL,0, NULL,NULL); char *buffer=new char[len+1]; WideCharToMultiByte(CP_ACP,0, str.c_str(), str.size(), buffer, len, NULL, NULL ); buffer[len]='\0'; //删除缓冲区并返回值 return_value.append(buffer); delete []buffer; return return_value; }
2.10.2 Win32 API中的字符串
Win32 API的字符串函数基本功能和C语言的字符串函数类似,但功能更强大、更安全。它们在strsafe.h中声明。缺乏统一性是导致现在许多C语言字符串处理函数容易产生安全漏洞的根本原因,而strsafe系列函数始终用HRESULT语句作为返回值,安全性要高得多。
在使用strsafe系列函数的时候,字符串必须以NULL字符结尾,并且我们一定要检查目标缓冲区的长度。
我们来看几个常用的strsafe函数。
(1)StringCchPrintf函数
函数StringCchPrintf用来格式化一个字符串并存入目标缓冲区中。该函数声明如下:
HRESULT StringCchPrintf( LPTSTR pszDest, size_t cchDest, LPCTSTR pszFormat, ...);
其中参数pszDest指向一段缓冲区,该缓冲区存放以NULL结尾的,被格式化的字符串;cchDest为目标缓冲区的长度,单位是字符个数,最大长度为STRSAFE_MAX_CCH; pszFormat指向一段缓冲区,该缓冲区存放的是printf风格的格式字符串。函数返回HRESULT,可以用宏SUCCEEDED和FAILED来判断函数正确与否。
StringCchPrintf是StringCchPrintfA和StringCchPrintfW的统一版本,该函数在功能上可以用来替换以下这些函数:sprintf、swprintf、wsprintf、wnsprintf、_stprintf、_snprintf、_snwprintf和_sntprintf。
下面的代码演示了StringCchPrintf函数的使用:
TCHAR pszDest[30]; size_t cchDest = 30; LPCTSTR pszFormat = TEXT("%s %d + %d = %d."); TCHAR* pszTxt = TEXT("The answer is"); HRESULT hr = StringCchPrintf(pszDest, cchDest, pszFormat, pszTxt, 1, 2, 3); //运行后pszDest的内容为 "The answer is 1 + 2 = 3."
(2)StringCchLength函数
该函数用来获取当前字符串的长度,单位是字符个数,长度不包括结尾字符NULL。函数声明如下:
HRESULT StringCchLength(LPCTSTR psz, size_t cchMax, size_t *pcch);
其中参数psz为要检查长度的字符串;cchMax为psz的最大长度,最大取值为STRSAFE_MAX_CCH; pcch用来获取psz实际包含的字符数,不包括结尾字符NULL。函数返回HRESULT,可以用宏SUCCEEDED和FAILED来判断函数正确与否。
strsafe系列函数有很多,这里我们列举的是后面章节会用到的几个函数。其实strsafe系列函数比CRT字符串函数有更高的安全性外,更是在某些不能使用CRT函数的场合下的替代品,比如CreateThread开辟的线程函数中不能使用CRT函数,此时strsafe系列函数就可以派上大用场了。
2.10.3 MFC中的字符串
CString类是MFC中常用的类,它用来操作字符串,该类功能十分强大,使得操作字符串非常方便。下面将简要介绍CString类对字符串的常见操作。
CString对象采用了动态分配内存的机制,即在创建CString对象时,不需对该对象指明内存大小,CString会根据实际情况动态地进行分配。类CString常用的成员函数见下表。

(1)CString对象的初始化
创建一个CString类对象并为其赋值的方法有以下几种方法。
第一种方法先构造一个CString类的对象,然后再使用赋值语句为其赋值,比如:
CString str; str = _T("大家好");
第二种方法是在构造CString类对象的同时,直接为其赋值,比如:
CString str(_T("大家好"));
第三种方法是在构造CString类对象的同时,利用其他CString对象为其赋值,比如:
CString str = str1; //str1也是一个str对象
第四种方法是在构造CString类对象的同时,采用单字符为其赋值,比如:
CString str(_T(’大’));
第五种方法是在构造CString类对象的同时,利用单字符加个数的形式为其赋值,比如:
CString str(_T(’大’), 6); //可以指定单字符的个数
(2)GetLength函数
CString的成员函数GetLength可以获取字符串中对象的字符个数。该函数声明如下:
int GetLength( ) const;
函数返回CString对象的字符个数。
在多字节字符集下,其值和字符串的字节数相同。比如:
CString s( "abcde" ); int len =s.GetLength(); //len的结果为5 CString str= _T("我abcde"); //’我’占用两个字节,相当于两个ANSI字符所占字节 int len = str.GetLength(); //len为7
而在Unicode字符集下,该函数值不是字节数,是字符数,比如:
CString str = _T("abcde"); int len = str.GetLength(); //len为5 CString str = _T("我abcde"); int len = str.GetLength(); //len为6
(3)Format函数
该函数用来格式化一个字符串。函数声明如下:
void Format( PCXSTR pszFormat, [, argument]...);
其中参数pszFormat是一个格式化字符串,Format用于转换的格式字符有:%c(单个字符)、%d(十进制整数,int型)、%ld(十进制整数,long型)、%f(十进制浮点数,float型)、%lf(十进制浮点数,double型)、%o(八进制数)、%s(字符串)、%u(无符号十进制数)、%x(十六进制数); argument为格式化字符串的参数。比如:
CString str; int number=15; str.Format(_T("%d"), number); //str="15" str.Format(_T("%4d"), number); //str=" 15",4表示将占用4位,15左边有2个空格 str.Format(_T("%.4d"), number); // str="0015" CString str; int num = 255; str.Format(_T("%o"), num); //str="377",8进制 str.Format(_T("%.8o"), num); //str="00000377" //double转换为CString: CString str; double num=1.46; str.Format(_T("%lf"), num); //str="1.46" str.Format(_T("%.1lf"), num); //str="1.5"(.1表示小数点后留1位,小数点后超过1位则四舍五入) str.Format(_T("%.4f"), num); //str="1.4600" str.Format(_T("%7.4f"), num); //str=" 1.4600"(前面有1个空格) //float转换为CString的方法也同上面相似,将lf%改为f%就可以了。
(4)IsEmpty函数
判断函数用来判断CString对象中的字符串是否是空的。函数声明如下:
BOOL IsEmpty( ) const;
如果CString对象的长度为0,则返回非零值;否则返回0。
比如下列两种情况都是为空。
CString s; ASSERT(s.IsEmpty()); s = _T(""); ASSERT(s.IsEmpty());
(5)连接两个CString对象
我们可以通过+或+=来连接两个CString对象,组成一个新的CString对象。比如:
CString s1 = _T("This "); // Cascading concatenation s1 += _T("is a "); CString s2 = _T("test"); CString message = s1 + _T("big ") + s2; // Message中的内容是"This is a big test".
(6)Left函数
提取字符串的左侧部分。函数原型如下:
CString Left( int nCount );
其中nCount指定要返回的字符个数。函数返回的字符串中包括原字符串左边的nCount个字符。比如:
CString s( _T("abcdef") ); ASSERT( s.Left(3) == _T("abc") );
(7)LoadString函数
从Windows资源加载现有CString对象。函数原型如下:
BOOL LoadString(UINT nID);
其中nID为字符串资源的ID。如果资源加载成功返回非零,否则为零。比如:
CString s; if (s.LoadString( IDS_FILENOTFOUND )) // IDS_FILENOTFOUND是字符串资源的ID AfxMessageBox(s); //加载成功,显示字符串内容
(8)Mid函数
提取字符串的中心部分。函数原型如下:
CString Mid(int iFirst, int nCount) ; CString Mid(int iFirst);
其中iFirst为要提取的字符串的第一个字符在原字符串中的索引;nCount为要提取的字符个数,如果未提供此参数,则iFirst索引右边的字符全部提取出来。函数返回指定范围内的字符串的CString对象。比如:
CString s( _T("abcdef") ); ASSERT( s.Mid( 2, 3 ) == _T("cde") );
(9)Remove函数
从字符串中移除字符。函数原型如下:
int Remove( CHAR chRemove);
其中参数chRemove为要移除的字符,字符chRemove是大小写敏感的。函数返回移除字符的个数,如果为零,则没有更改字符串。比如:
// 从一个句子中移走小写字母’t': CString str ("This is a test."); int n = str.Remove('t'); ASSERT( n == 2); ASSERT( str =="This is a es.");
(10)AppendFormat函数
该函数在当前CString的字符串后追加一个格式化的字符串。函数原型如下:
void AppendFormat( PCXSTR pszFormat, [, argument]...);
其中参数pszFormat是一个格式化字符串,Format用于转换的格式字符有:%c(单个字符)、%d(十进制整数,int型)、%ld(十进制整数,long型)、%f(十进制浮点数,float型)、%lf(十进制浮点数,double型)、%o(八进制数)、%s(字符串)、%u(无符号十进制数)、%x(十六进制数); argument为格式化字符串的参数。比如:
CString str = _T("Result: "); str.AppendFormat(_T("X value = %.2f\n"), 12345.34644);
结果是12345.35,要注意四舍五入。
(11)GetBuffer函数
该函数用来获取CString对象内部使用的字符缓冲区或获取新申请的内存缓冲区。函数声明如下:
LPTSTR GetBuffer( ); LPTSTR GetBuffer(int nMinBufferLength);
第一个函数相当于第二个函数参数为0的情况,它返回CString对象内部使用的字符缓冲区的指针。
第二个函数中,参数nMinBufferLength指定要获取的缓冲区大小。如果nMinBufferLength小于等于当前字符串长度,函数并不分配内存,而是返回CString对象内部使用的字符缓冲区的指针;如果nMinBufferLength大于当前字符串长度,则会重新分配大小为nMinBufferLength的缓冲区,然后把CString对象的当前缓冲区给销毁掉,并返回新分配的缓冲区指针。
有了CString对象的字符缓冲区的指针时,我们可以修改CString字符串中的内容了。要注意的是,如果我们通过GetBuffer返回的指针修改了CString字符串中的内容,CString内部的一些状态信息,比如CString的字符串长度变量记录的值和当前字符串真正的长度不同了,此时如果再调用一些CString的成员函数,尤其是那些需要知道CString当前字符串长度的成员函数(比如AppendFormat)就会发生错误了。解决的办法是通过GetBuffer返回的指针修改了CString字符串中的内容后,要调用成员函数ReleaseBuffer,该函数可以重新获取字符串当前的长度,并同步更新到CString内部用于记录字符串长度的变量中。当前如果GetBuffer以后程序结束运行退出了,作为局部变量的CString对象都不存在了,调用不调用ReleaseBuffer没什么意义了。
示例1:把读取文件的内容存放在GetBuffer获取的缓冲区中。
void ReadFile(CString& str, const CString strPathName) { FILE* fp = fopen(strPathName, "r"); // 打开文件 fseek(fp, 0, SEEK_END); int nLen = ftell(fp); // 获得文件长度 fseek(fp, 0, SEEK_SET); // 重置读指针 char* psz = str.GetBuffer(nLen); fread(psz, sizeof(char), nLen, fp); //读文件内容 fclose(fp); str.ReleaseBuffer(); //这一句最好写上,让str知道当前字符串的实际长度。 }
上面代码一下子把某个文本文件中内容读取到str的字符缓冲区中。要注意的是,如果文本文件中有回车(‘\r')换行(‘\n'),读取的时候会把\r\n转为\n后再保存到缓冲区中。另外,最后一句ReleaseBuffer最好加上,否则str.GetLength返回的长度是0。ReleaseBuffer的作用下面会讲到。
示例2:CString转为TCHAR*
CString s(_T('This is a test ')); LPTSTR p = s.GetBuffer();
(12)ReleaseBuffer函数
更新CString内部记录字符串长度的变量。函数原型如下:
void ReleaseBuffer(int nNewLength = -1);
其中参数nNewLength为要更新给内部记录长度变量的值,如果为-1,则会在字符串中查找‘\0',如果没有找到,则把当前CString内部字符缓冲区的大小赋值给内部记录长度的变量。因此,如果当前字符串是以‘\0’结尾的,可以使用-1或不用参数。比如:
CString s="abc"; LPTSTR p = s.GetBuffer(1024); memcpy(p, "123456789",9); // 给缓冲区赋值 p[9] = '\0'; int len = s.GetLength(); // GetLength还是3,已经和实际长度不一致了 s.ReleaseBuffer(); // 释放多余的内存,现在p无效。 len = s.GetLength(); // GetLength变为9了,和实际长度一致了
上面代码中,如果把p[9]='\0';去掉,则最后一句的len为1024,因为ReleaseBuffer找不到‘\0’了。
(13)整型、长整型转为CString
转为整型可以利用函数itoa,比如:
CString str; itoa(i, str,10); /将i转换为字符串放入str中,最后一个数字表示十进制 itoa(i, str,2); //按二进制方式转换
如果是长整型转为CString,可以利用函数ltoa。
或者利用CString的Fomat成员函数。
(14)CString转为整型、长整型、浮点型
利用函数atoi,比如:
CString str="12345"; int i = atoi(str);
如果要转为长整型,可以使用函数atol;如果要转为float型,可以使用函数atof。
(15)char*转为CString
如果当前工程使用多字节字符集,可以用下列方法进行转换:
CString str; char sz[]="世界,您好!helloworld"; str.Format("%s", sz); //利用Fomat函数 str = (CString)sz; //强制转换
如果当前工程使用Unicode字符集,通常有以下几种方法可以进行转换:
第一种方法是使用API函数MultiByteToWideChar进行转换,比如:
char * pFileName = "世界,您好!Hello, World"; //计算char *数组大小,以字节为单位,一个汉字占两个字节 int charLen = strlen(pFileName); //计算多字节字符的大小,按字符计算。 int len = MultiByteToWideChar(CP_ACP,0, pFileName, charLen, NULL,0); //为宽字节字符数组申请空间,数组大小为按字节计算的多字节字符大小 TCHAR *buf = new TCHAR[len + 1]; //多字节编码转换成宽字节编码 MultiByteToWideChar(CP_ACP,0, pFileName, charLen, buf, len); buf[len] = '\0'; //添加字符串结尾,注意不是len+1 //将TCHAR数组转换为CString CString str; str.Append(buf); //删除缓冲区 delete []buf;
第二种方法是使用函数A2T或A2W,比如:
char * p = "世界,您好!Hello, World"; USES_CONVERSION; //这个宏在atlbase.h中定义。 CString s = A2T(p); CString s2 = A2W(p);
(16)CString变量转为char*
如果当前工程使用多字节字符集,可以用下列方法进行转换:
CString str = "长城"; char *p = (LPSTR)(LPCTSTR)str; //强制转换 p = str.Getbuffer(); //利用成员函数GetBuffer
如果当前工程使用Unicode字符集,通常有两种方法:
第一种方法是使用API函数WideCharToMultiByte进行转换,比如:
CString str = _T("世界,您好!Hello, World"); //注意:以下n和len的值大小不同,n是按字符计算的,len是按字节计算的 int n = str.GetLength(); //获取宽字节字符的大小,大小是按字节计算的 int len = WideCharToMultiByte(CP_ACP,0, str, str.GetLength(), NULL,0, NULL, NULL); //为多字节字符数组申请空间,数组大小为按字节计算的宽字节大小 char * pFileName = new char[len+1]; //以字节为单位 //宽字节编码转换成多字节编码 WideCharToMultiByte(CP_ACP,0, str, str.GetLength(), pFileName, len, NULL, NULL); pFileName[len+1] = '\0'; //多字节字符以\0结束
第二种方法是使用函数T2A或W2A,比如:
CString str = _T("世界,您好!Hello, World"); //声明标识符 USES_CONVERSION; // 这个宏在atlbase.h中定义。 //调用函数,T2A和W2A均支持ATL和MFC中的字符转换 char * p = T2A(str); char * q = W2A(str); //效果同上行