《实用VC编程之玩转字符串》

《实用VC编程之玩转字符串》第01课 宽窄字节的区别及重要性



1、宽窄字节简介:
什么是宽字节,什么是窄字节?对于新手来说可能比较迷糊,我当初学习的时候也是这样!之前学习了C语言/C++语言,使用的字符串指针就是 char* 类型,C++中的字符串是 string,内部也是对 char* 的封装,那么这些跟宽窄字节有什么关系呢?

其实最早的系统都是窄字节的,也就是我们很常用的 char 因为都是英文的,英文本身就26个字母,再加上其他的一些标点符号之类的,char 也能表示的下,无符号的 char 最多能表示 255个字符,对吧,所以足够用了!

随着,操作系统的国际化,比如:Windows 系统不仅有英文的,也有中文的,韩文的,日文的,所以原来用一个 char 来表示一个英文字符的方式已经无法表达中文的一个汉字了。汉字是很多的,好几万个,单纯的 char 的取值范围已经无法表达的下了。这时候有大牛就想到了,既然用一个char表示不下,那么就用2个char来表示一个汉字,这样就可以解决了,所以窄字节的表示方法就是数字、字母之类的仍然用一个char来表示,一个汉子或者全角字符使用2个 char 来表示。

没错,这样可以解决大多数问题,在中文的系统上能正常的显示中文,在日文的系统上也能正常的显示日文,但是如果把一个在中文系统上写的软件,界面上带有汉字的程序拿到一个日文的Windows操作系统上就会有问题了,乱码了,汉字无法正常显示,同理,把一个界面上带有日文或者韩文的软件拿到中文的系统上也显示乱码!

为了解决这个国际化的问题,微软在Windows操作系统中引入了宽字节的功能,即:Unicode,Unicode中规定任意一个字符都占用两个字节的存储空间,即2个char,不管是数字或者字母,还是一个汉字 都占用2个字节。用两个char难免不方面,所以微软直接使用一个新的类型:wchar_t,大家看起来比较陌生,不过他的原型实际上就是 unsigned short,这个大家比较熟悉吧,占用2个字节的存储空间。

所以,微软就是利用Unicode编码来解决这个国际化的问题!

2、操作系统及VS编译器对宽窄字节的编码支持:
①、Windows操作系统提供了两种类型的 API 函数,例如 MessageBox 函数,其实 MessageBox 他只是一个宏,他对应的两个版本的函数分别为:MessageBoxA 和 MessageBoxW,你在VS编译器中使用的时候系统会根据是否定义了_UNICODE 宏来进行判断当前工程使用的是宽字节的Unicode编码还是窄字节编码,根据这个来决定该使用哪个版本的函数!如果你的工程没有定义 _UNICODE 宏,也就是非Unicode编码,那么就使用窄字节的 MessageBoxA,如果定义了,那么就使用宽字节的 MessageBoxW,具体在代码中,右键找定义,见演示!

查看DLL中的导出函数可以使用 depends 这个工具来查看!

备注:Windows 2000 及其以后的 Xp、2003、Vista、Win7、Win8、Win10 等系统都是使用Unicode从头进行开发的,如果调用任何一个Windows API 函数并给它传递一个 ANSI 字符串,那么系统首先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。如果希望函数返回ANSI字符串,系统就会先将Unicode字符串转换成ANSI字符串,然后将结果返回给你的应用程序。进行这些字符串的转换需要占用系统的时间和内存。通过从头开始用Unicode来开发应用程序,就能够使你的应用程序更加高效的运行!

②、编译器对宽窄字节的支持:
VC++ 6.0 默认为窄字节编码,vs2005、vs2008、vs2010、vs2012、vs2013、vs2015、vs2017 等默认都是Unicode编码,当然可以进行工程的设置从而进行编码的转换,见演示!
备注:从vs2013开始,如果要让工程从默认的Unicode编码转到窄字节编码,需要安装vs2013的多字节补丁才行!
具体在工程的属性对话框中的如下位置可更改编码类型:

3、宽窄字符串的优缺点:
上面说了那么多,可能大家认为既然 Unicode 这么好,那以后我程序当中所有的字符串就都用Unicode的宽字节就行了吧?实际也不是的,宽字节也有缺点!
一般来说只是涉及到界面,或者是跟字符串操作相关的建议大家使用宽字节,其他地方还是可以用窄字节。因为宽字节的占用空间比窄字节多了一倍,所以如果是单纯在本机的话还好,如果是进行字符串的网络传输,那么传输量就会是窄字节的二倍,所以这块也不是说什么时候都一味的用Unicode宽字节。

那么,既然在同一个工程代码中,有的地方用宽字节,有的地方用窄字节,那么怎么来统一呢,这个时候肯定要进行宽窄字节的一个转换的工作了,不过还好,有多种方法可以进行转换,微软也为我们提供好了相应的 API 函数,我们直接拿过来用就可以了。关于转换这块我们之后会讲解的!

4、复杂的宽窄字节数据类型:
刚开始学编程的时候我们就接触了 char、char* 之类的,属于窄字节的,按照刚刚讲的,又多了一个 wchar_t 类型的字符,字符串指针的话就可以是 wchar_t* 类型。但是,在我们平时的编程过程中还会见到很多其他的复杂类型,如下:
● 窄字节:

charchar *const char *
CHAR、(PCHAR、PSTR、LPSTR)、LPCSTR

● Unicode 宽字节:

wchar_twchar_t *const wchar_t *
WCHAR、(PWCHAR、PWSTR、LPWSTR)、LPCWSTR

● T 通用类型:

TCHAR、(TCHAR * 、PTCHAR、PTSTR、LPTSTR)、LPCTSTR

其中:P代表指针的意思,STR代表字符串的意思,L是长指针的意思,在WIN32平台下可以忽略,C代表const常量的意思,W代表wide宽字节的意思,T大家可以理解为通用类型的意思,
就是可以根据工程中是否定义_UNICODE 宏,来判断当前工程的编码类型是宽字节还是窄字节,之后分别定义成不同的类型,比如:TCHAR 类型,如果工程中定义了_UNICODE 宏,那么就表明工程是宽字节编码,他最终就被定义成 wchar_t 类型,如果工程中没有定义_UNICODE 宏,就表明工程当前是窄字节编码,那么 TCHAR 被最终定义成 char 类型。

第02课 宽窄字节字符串的使用


1、宽窄字符串类型指针的定义:
● 窄字节:char *p_str = “hello”;
● Unicode宽字节:wchar_t *p_wstr = L"hello";
● 通用类型:TCHAR *p_tstr = _T(“hello”); 或者 TCHAR *p_tstr= _TEXT(“hello”);
● 动态申请内存:TCHAR *pszBuf = new TCHAR[100];

其中,_TEXT 和 _T 是一样的,定义如下:

#define _T(x)       __T(x)
#define _TEXT(x)    __T(x)

来看看 __T 的最终定义:

#ifdef  _UNICODE
#define __T(x)      L##x
#else
#define __T(x)      x
#endif

其中,##为连接的意思。

2、常用的宽窄字节字符串处理函数:
字符串长度:

● Ansi:strlen(char *str);
● Unicode:wcslen(wchar_t *str);
● 通用函数:_tcslen(TCHAR *str);

● Ansi:int atoi(const char *str);
● Unicode:int _wtoi(const wchar_t *str);
● 通用函数:_tstoi(const TCHAR *str);

字符串拷贝:
● Ansi:strcpy(char *strDestination, const char *strSource);
● Unicode:wcscpy(wchar_t *strDestination, const wchar_t *strSource);
● 通用函数:_tcscpy(TCHAR *strDestination, const TCHAR *strSource);

以上函数不安全,在vs2003等以上版本的编译器中会有warnning警告提示,以下为安全函数(VC++6.0不支持):

● Ansi:strcpy_s(char *strDestination, size_t numberOfElements, const char *strSource);
● Unicode:wcscpy_s(wchar_t *strDestination, size_t numberOfElements, const wchar_t *strSource);
● 通用函数:_tcscpy_s(TCHAR *strDestination, size_t numberOfElements, const TCHAR *strSource);

numberOfElements
Size of the destination string buffer. 目的缓冲区的大小,以字节为单位,不是字符!

size_t unsigned integer,在MSDN中的解释:Result of sizeof operator,也就是说 size_t 是 unsigned integer 即无符号整数。那为什么会有size_t这个类型呢?
因为不同平台的操作系统(32/64)中 int/long 等类型所占的字节并不一样,而 size_t 在不同的平台下有不同的定义。有点类似于TCHAR类型:

#ifndef   _SIZE_T_DEFINED
  #ifdef     _WIN64
  typedef   unsigned   __int64         size_t;   //8个字节,64位
  #else
  typedef   _W64   unsigned   int       size_t;   //4个字节,32位
  #endif
  #define   _SIZE_T_DEFINED
#endif

3、sizeof 求宽窄字节字符串的注意事项:
char* p_str = “hello”;
我想求这个字符串所占用的字节数:sizeof(p_str) 肯定是错误的!
strlen(p_str) + 1,实际上就是 p_str 指针指向的字符串所占用的空间对吧?因为本身1个char占用1个字节的存储空间,所以默认字符串的长度加上字符串的结束符 \0 就是字符串所占用的字节数。

那么宽字节呢?wchar_t* p_wstr = L"hello";
他占用的字节数应该如何来求?
wcslen(p_str) + 1 这个对吗?当然也是不对的,正确的是:(wcslen(p_str) + 1) * sizeof(wchar_t)

通用类型:TCHAR* p_tstr = _T(“hello”);
(_tcslen(p_tstr) + 1) * sizeof(TCHAR)

应用在哪里呢?比如:
TCHAR *pszBuf = new TCHAR[100];
定义完了之后,我要给这个字符串数组清空,于是我应该这么做:
memset(pszBuf, 0, 100 * sizeof(TCHAR)) 这样做是最安全的,不管当前的 TCHAR 是 char 也好,是 wchar_t 也好,都可以满足。

第03课 宽窄字节字符串的转换



1、为什么要进行转换?
既然宽窄字节字符串各自的基本使用都给大家讲解完了,那么本节课开始呢,就给大家讲解下如何进行宽窄字节字符串的转换。
之前跟大家说过,并不是建议程序中所有的字符串都用宽字节的Unicode来表示,比如网络发送的字符串就可以用窄字节的,当对方收到之后默认收到的是窄字节的,因为对方的程序可能用宽字节Unicode来写的界面,所以要显示的时候就要转换成Unicode宽字节的字符串。这样就涉及到宽窄字节的转换,类似的情况很常见,所以本节课重点讲解下!

2、使用微软提供的API函数来实现宽窄字节的转换:
WideCharToMultiByte 实现宽字节转换到窄字节
MultiByteToWideChar 实现窄字节转换到宽字节

WideCharToMultiByte 的代码页用来标记与新转换的字符串相关的代码页;
MultiByteToWideChar 的代码页用来标记与一个多字节字符串相关的代码页,

函数原型:

int MultiByteToWideChar(
 
  UINT CodePage,
 
  DWORD dwFlags,
 
  LPCSTR lpMultiByteStr,
 
  int cchMultiByte,
 
  LPWSTR lpWideCharStr,
 
  int cchWideChar
 
  );

[1]、常用的代码页有 CP_ACP 和 CP_UTF8 两个:
使用 CP_ACP 代码页就实现了 ANSI 与 Unicode 之间的转换;— 我们所用的!
使用 CP_UTF8 代码页就实现了 UTF-8 与 Unicode 之间的转换,在网页中经常会用到UTF8编码的格式。

[2]、dwFlags 参数允许我们进行额外的控制,但是,一般情况下都不使用这个标志,直接传递 0 就行了。

[3]、lpDefaultChar和pfUsedDefaultChar:只有当WideCharToMultiByte函数遇到一个宽字节字符,而该字符在uCodePage参数标识的代码页中并没有它的表示法时,WideCharToMultiByte函数才使用这两个参数。如果宽字节字符不能被转换,该函数便使用lpDefaultChar参数指向的字符。如果该参数是NULL(这是大多数情况下的参数值),那么该函数使用系统的默认字符。该默认字符通常是个问号。这对于文件名来说是危险的,因为问号是个通配符。pfUsedDefaultChar参数指向一个布尔变量,如果Unicode字符串中至少有一个字符不能转换成等价多字节字符,那么函数就将该变量置为TRUE。如果所有字符均被成功地转换,那么该函数就将该变量置为FALSE。当函数返回以便检查宽字节字符串是否被成功地转换后,可以测试该变量。

● 两个转换函数的使用举例:

char *cctryWideCharToAnsi(wchar_t *pWideChar)
{
     
    if (!pWideChar) return NULL;
    char *pszBuf = NULL;
    int needBytes = WideCharToMultiByte(CP_ACP, 0, pWideChar, -1, NULL, 0, NULL, NULL);
    if (needBytes > 0)
    {
     
        pszBuf = new char[needBytes+1];
        ZeroMemory(pszBuf, (needBytes+1)*sizeof(char));
        WideCharToMultiByte(CP_ACP, 0, pWideChar, -1, pszBuf, needBytes, NULL, NULL);
    }

    return pszBuf;
}
wchar_t *cctryAnsiCharToWide(char *pChar)
{
     
    if (!pChar) return NULL;
    wchar_t *pszBuf = NULL;
    int needWChar = MultiByteToWideChar(CP_ACP, 0, pChar, -1, NULL, 0);
    if (needWChar > 0)
    {
     
        pszBuf = new wchar_t[needWChar+1];
        ZeroMemory(pszBuf, (needWChar+1)*sizeof(wchar_t));
        MultiByteToWideChar(CP_ACP, 0, pChar, -1, pszBuf, needWChar);
    }

    return pszBuf;
}

注意:使用过后千万别忘记释放返回的指针空间。
具体代码的讲解详见视频教程!

3、使用ATL提供的 CA2W、CW2A、CT2W、CT2A 来更方便的转换:
之前在《VC++基础班》中曾给大家讲解过 A2W、W2A、T2A、T2W 之类的宏也可以方便的转换,但是这几个宏出现的时间比较早,如果字符串很长的话就会有问题,所以这里面不再建议大家使用了。介绍给大家更为方便和安全的 CA2W、CW2A、CT2W、CT2A 封装类来给大家使用。

具体怎么样来用,给大家演示一下:
示例1:

wchar_t* p_wstr = L"你好123";
char* p_str = "大家好456";
TCHAR* p_tstr = _T("世界好789");

CW2A w2aObj(p_wstr);
char* p_dst = (char*)w2aObj;

CA2W a2wObj(p_str);
wchar_t* p_wdst = (wchar_t*)a2wObj;

CT2A t2aObj(p_tstr);
char* pt = (char*)t2aObj;

示例2:
包含头文件:#include
1、A2W和W2A
在《Window核心编程》,多字节和宽字节之间转换比较麻烦的,MultiByteToWideChar函数和WideCharToMultiByte函数有足够多的参数的意义让我们去理解。那么使用ATL的一个很好的字符串的转换宏:A2W和W2A。
(1)A2W的用法:

1.USES_CONVERSION;  
2.CString tmpStr;  
3.char*LineChar="fdsfdsa";  
4.const WCHAR * cLineChar = A2W(LineChar);  
5.tmpStr=cLineChar;  

(2)W2A的用法:

1.USES_CONVERSION;  
2.CString tmpStr;  
3.WCH LineChar="fdsfda";  
4.const char* cLineChar = A2W(LineChar);  

2、A2T和T2A
(1)A2T的用法:

1.USES_CONVERSION;  
2.char * pChar="char to cstring";  
3.CString cTemp=A2T(pChar);  

(2)T2A的用法:

1.USES_CONVERSION;  
2.CString cTemp =_T("char to cstring");  
3.char * pChar=A2T(pChar); 

第04课 CString的方便之处及优缺点



1、CString简介:
提起CString,相信接触过MFC编程开发的小伙伴都不陌生,相反很熟悉。MFC中对于CString字符串的使用可以说是很频繁的,CString类也可以说是MFC中针对字符串的一个封装类。其实CString归属于ATL,不过在MFC中一样使用,而且被重点使用。

2、CString类的方便之处:
在 C语言中,对于字符串的操作有字符串数组,字符串指针之类的,比如:
char* p_str = “hello”;
char szbuf[100] = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’};
再对字符串进行拷贝、连接、比较 的时候也要借助 strcpy、strcat、strcmp 之类的函数,而且还要考虑目标空间是否够用之列的,很是麻烦,一旦操作不好就会导致数组越界了,造成缓冲区溢出。

有的网友会说了,C语言的字符串处理确实比较麻烦,但是我可以使用C++ STL 中的 string 啊,这样要比C语言的方便很多吧,比如:
string str = “hello”;
str += " world.";
bool is_equal = (str == “123”);
这样就不用担心字符串空间是否够用,连接、比较什么的也十分方便,直接 += 或者 == 就可以判断出来了。其实 C++ STL 中的 string 还提供了很多其他的算法,例如:
Cplusplus中文网站介绍CSTring
http://www.cplusplus.com/reference/string/string/

虽然 C++ STL 中的 string 照比C语言中的字符串处理方便很多,但是这里我要说,跟成熟的字符串处理还是差很多,起码跟 CString来说就差了不少。比如:
trim 操作:去除掉首尾的不可见字符,比如回车,制表符,空格之类的;
reverse 操作:进行字符串的首尾颠倒反转;
upper操作:将字符串中的英文全变成大写字母;
lower操作:将字符串中的英文全变成小写字母;
right操作:直接返回字符串中结尾的指定字符;
span_including操作:返回包含指定字符串中任意一个字符的子串
span_excluding操作:返回不包含指定字符串中任意一个字符的子串
format:格式化字符串
replace:替换字符串中的指定字符
stricmp:不区分大小写进行字符串比较

等等吧,这些都没有现成的函数,不可能类似这样简单的操作:
string str = “hello”;
str.upper();
直接将字符串变成大写字母的。实际是做不到的,即使能做到也需要好几行代码。但是以上这些操作在 CString 中就相对简单了。可以很轻松的做到,CString早就封装好了这些常用的操作。
例如:
CString str;

3、CString 对于 TCHAR 的封装:
针对当前编码,决定当前内部的指针是 char* 还是 wchar_t*,实际上CString 本身是个模板类,实际上有三种:

typedef ATL::CStringT< wchar_t, StrTraitMFC< wchar_t > > CStringW;
typedef ATL::CStringT< char, StrTraitMFC< char > > CStringA;
typedef ATL::CStringT< TCHAR, StrTraitMFC< TCHAR > > CString;

CStringA 内部使用的是 char*,CStringW 内部使用的是 wchar_t*,CString内部使用的是 TCHAR*,所以 CString 本身会根据当前工程的编码决定使用 char* 还是 wchar_t*,这一点也比较方便。
我们可以先简单使用下 CString,之后改变下工程的编码,看看编译结果就知道了。

4、CString 类对于宽窄字节的转换:
当前工程编码是宽字节Unicode的前提下,CString支持从char* 类型的窄字节字符串来构造和赋值。所以,是可以方便的从窄字节转换到宽字节。但是反之则没有办法实现。

5、使用 CString 类的优缺点:
先来说说优点:
①、使用方便,包含了很多实现好的操作,包括:trim、reverse、format、replace 等等;
②、无缝兼容MFC;
③、自适应当前工程编码,匹配宽窄字节;
④、能实现从窄字节到宽字节的自动转换工作;

再来说说缺点:
①、在非MFC的工程中无法使用,例如:控制台类型的工程,或者Win32类型的API项目;
②、无法完全实现宽窄字节的任意转换;
③、非C++标准,无法跨平台,在除了VC以外的编译器无法使用,更无法在Linux等其他平台上使用。

6、解决方案:
扩展 C++ 标准模板库 STL 中的 string 字符串,使其既方便使用,又可以自由的转换宽窄字节。

你可能感兴趣的:(字符串,c++)