Visual C++2013从入门到精通(视频教学版)
上QQ阅读APP看书,第一时间看更新

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); //效果同上行