【windows核心编程】第二章 字符和字符串处理

第二章 字符和字符串处理

 

1、 几种字符集

尽量使用Unicode来代替ANSI字符串 

关于双字节字符集 (double-byte character set, DBCS), 双字节字符集中,一个字符串中每个字符由一个或两个字节组成,处理起来不方便。 

关于UTF-8,UTF-8的编码规则是将一些字符编码为1字节,一些为2字节,一些为3字节,一些为4字节;非常流行,但对值为0x0800及以上的大量字符进行编码的时候不如UTF-16方便。 

关于UTF-16,在windows VISTA中,每个字符都使用UTF-16来编码, UTF-16为每个字符编码为2个字节,一般情况下unicode指的就是UTF-16编码,.NetFramwork始终使用UTF-16来编码。 

关于UTF-32, 为每个字符都使用4字节来编码,比较浪费空间。 

 

结论:以后的程序中都尽量使用UNICODE格式,兼容性和国际化比较方便。

 

2、 ANSI和UNICODE

ANSI字符即以8位char数据类型表示的字符;

MS C/C++编译器定义了一个内建的数据类型wchar_t,即宽字符,它表示一个UTF-16字符。在visual studio 2005中,项目属性--配置属性—C/C++--语言:【将wchar_t视为内置类型】可以选择。

在MS编译器对wchar_t的支持之前,有一个C头文件定义了一个wchar_t数据类型,如下:

Typedef unsigned short wchar_t;  //无符号短整数

 

定义unicode变量:wchar_t ch = L’A’; //定义了一个宽字符’A’

 

关于L, _T, __T, _TEXT, __TEXT, TEXT的区别:

L :      把字符串定义为宽字符串

_T或__T: 如果定义了_UNICODE 则为宽字符,否则不是,tchar.h

_TEXT: 如果定义了_UNICODE则为宽字符,否则不是,tchar.h

 

__TEXT或TEXT:如果定义了UNICODE宏则为宽字符,否则不是,winnt.h

 

_UNICODE宏用于C运行期头文件,而UNICODE宏则用于Windows头文件,_UNICODE和UNICODE要么都不定义,要么全都定义。

尽量不要使用L,而是要使用其他的条件宏。

 

 1 PCHAR == CHAR*
 2 
 3 PSTR  == CHAR*
 4 
 5 PCSTR == CONST CHAR*
 6 
 7 PWCHAR == WCHAR*
 8 
 9 PWSTR  == WCHAR*
10 
11 PCWSTR == CONST WCHAR*

 

 

 

3、 关于windows函数中的unicode和ansi函数

一个比较常用的函数MessageBox, CWnd类的成员函数,其实是一个宏,定义如下:

1 #ifdef UNICODE
2 
3 #define MessageBox MessageBoxW
4 
5 #else
6 
7 #define MessageBox MessageBoxA
8 
9 #endif

 

 

在上面两个版本的函数中(W 和 A),如果是A版本,那么函数内部会把ANSI字符(串)参数转换为unicode版本再在内部调用W版本,W版本函数返回时再把相应的输出转换为ANSI格式。 这个过程是需要额外的时间和空间的,因此鼓励使用UNICODE版本。

 

 

4、 C运行库中的unicode函数和ansi函数

与windows函数不同的是:C运行库函数的ansi版本不会和windows函数那样在内部转换并调用unicode版本,而是“自力更生”,自己完成函数的执行过程。

   在C运行库中,strlen函数返回一个ansi字符串的长度(不包括结尾的’\0’),与之对应的unicode版本的是wcslen,返回unicode字符串的长度(同样不包括’\0’)。这两个函数的原型都在string.h中,为了自动适应哪种类型的字符串还必须包含tchar.h,该头文件定义了如下宏:

1 #ifdef _UNICODE
2 
3 #define _tcslen   wcslen
4 
5 #else
6 
7 #define _tcslen   strlen
8 
9 #endif

 

 

C运行库的字符串函数:strcpy, wcscpy建议放弃,因为没有指定输入缓冲区的大小。

 

5、 C运行库的安全字符串函数

这些函数包含在strsafe.h中,这个头文件包含了string.h,现在每一个不安全的函数都对应一个安全版本,如_tcscpy_s, _tcscat_s,这些函数原型中添加了一个缓冲区大小的参数,字符个数,可以用_countof来获取缓冲区大小(实践证明包含’\0’),_countof在stdlib.h中定义。

 

安全函数会检查指针不为NULL, 整数在有效范围内,枚举值是有效的,缓冲区足以容纳结果数据等。

C运行时允许我们提供自己的函数来替代C运行时引起的 Debug Assertion Failed(断言失败)。 可见cnblogs中2013.01.21的博客。

 

  

除了上面说的新的_s安全字符串函数,C运行库还增加了一些函数,用于在执行字符串处理时提供更多的控制。例如可以控制填充字符、指定如何截断。这些函数有ANSI版本和UNICODE版本。

 1 HRESULT StringCchCat(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc);
 2 
 3 HRESULT StringCchCatEx(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc,
 4 
 5 PTSTR *ppszDestEnd, size_t *pcchRemaning, DWORD dwFlags);
 6 
 7  
 8 
 9 HRESULT StringCchCopy(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc);
10 
11 HRESULT StringCchCopyEx(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc,
12 
13 PTSTR *ppszDestEnd, size_t *pcchRemaining, DWORD dwFlags);
14 
15  
16 
17 HRESULT StringCchPrintf(PTSTR pszDest, size_t cchDest, PCTSTR pszFormat, …);
18 
19 HRESULT StringCchPrintfEx(PTSTR pszDest, size_t cchDest, PTSTR *ppszDestEnd, size_t *pchRemaining, PCTSTR pszFormat, …);

 

 

函数名中的Cch表示字符串,即Count of characters. 另外还有Cb的版本,即count of bytes,字节数。

关于HRESULT返回值,返回S_OK成功,返回STRSAFE_E_INVALID_PARAMETER说明将NULL值传给了一个参数, 返回STRSAFE_E_INSUFFICIENT_BUFFER说明目标缓冲区太小,无法容纳整个源字符串。

 

扩展版本的参数

Size_t *pcchRemaining: 指向一个变量的指针,返回往缓冲区写完后缓冲区剩余的字符数,包括’\0’。如果pcchRemaing为NULL则不返回计数。

LPTSTR *ppszDestEnd: 如果ppszdDestEnd不为NULL则它将指向终止字符’\0’。

 

DWROD deFlags:以下一个或多个值

STRSAFE_FILL_BEHIND_NULL:如果函数成功,dwFlags用低字节来填充目标缓冲区的剩余部分(即’\0’之后的部分), 如果用STRSAFE_FILL_BYTE来代替它,那么将用指定的字符来填充目标缓冲区剩余的部分。

 

STRSAFE_IGNORE_NULLS:把NULL字符串指针视为TEXT(“”);

 

STRSAFE_FILL_ON_FAILURE:如果函数失败,就用dwFlags的低字节来填充整个目标缓冲区,但目标缓冲区的最后一个字符会被设为’\0’, 如果缓冲区太小从而失败原因是STRSAFE_E_INSUFFICIENT_BUFFER,那么在返回的字符串中,所有的字符都会被替换成填充字符。

  

STRSAFE_NULL_ON_FAILURE:如果函数失败,就将目标缓冲区的第一个字符设为’\0’,从而是缓冲区字符串成为一个空字符串TEXT(“”),如果失败的原因是STRSAFE_E_INSUFFICIENT_BUFFER,则截断后的字符都会被覆盖。

 

STRSAFE_NO_TRUNCATION:同STRFAFE_NULL_ON_FAILURE ?

  

从这几个flags的定义可以看出:其低字节为0x00,即终止字符’\0’。

 

 

6、 windows字符串函数

 

比如lstcat和lstrcpy已经不赞成使用,因为他们无法检测缓冲区益处问题。

与此同时,在shlwapi.h中定义了大量好用的字符串函数,同时还需要shlwapi.lib文件;比如StrForamtKBSize, StrFormatByteSize等。

其实这两个函数都是宏,都有两个W和A版本的函数。

StrForamtKBSize函数的W版本原型如下:

LWSTDAPI_(LPWSTR)   StrFormatKBSizeW(LONGLONG qdw, __out_ecount(cchBuf) LPWSTR pszBuf, UINT cchBuf);

 

 

1 //eg.
2 
3 LPWSTR pszBuffer = new WCHAR[100];
4 
5 memset(pszBuffer, 0, sizeof(WCHAR) * 100);
6 
7 LPWSTR pszRet = StrFormatKBSize(12345, pszBuffer, 100);

 

 

结果: *pszRet == *pszBuffer == L”13 KB”; psRet指向pszBuffer指向的内存。

 

另有CompareString(Ex)和CompareStringOrdinal

 

int CompareString(  //此函数是一个宏 
  LCID locale,  //区域设置ID,标识一种语言, 可用LCID GetThreadLocale()获得 
  DWORD dwCmdFlags, //比较的方式 
  PCTSTR pString1, //字符串1 
  int cch1,         //字符串1字符数 
  PCTSTR pString2, //字符串2 
 int cch2         //字符串2字符数 
);

 

 

dwCmdFlags:

NORM_IGNORECASE

LINGUISTIC_IGNORECASE : 忽略大小写 

 

NORM_IGNOREKANATYPE:不区分平假名和片假名字符 

 

NORM_IGNORENONSPACE

LINGUISTIC_IGNOREDIACRITIC:忽略non-spacing字符

 

NORM_IGNORESYMBOLS:忽略符号

 

NORM_IGNOREWIDTH:不区分同一个字符的单字节和双字节形式

 

SORT_STRINGSORT:将标点符号当作符号来处理

 

1 int CompareStringOrdinal(    //只有UNICODE版本
2 
3   PCWSTR pString1,  //字符串1 
4    int cchCount1, 
5   PCWSTR pString2, 
6   int cchCount2, 
7   BOOL bIgnoreCase
8 
9 );

 

 

CompareStringOrdinal用于比较程序内部所用的字符串(如路径名、注册表项/值、XML元素/属性等),这个函数不需要LCID,速度快。

上面两个函数的返回值需要注意:返回0说明失败,返回1说明前者小于后者,返回2说明相等,返回3说明前者大于后者。可以把返回值减去2得到结果来与strcmp_s或strcmp的结果保持一致。

 

 

 

7、 推荐的字符串处理方式

使用C运行库的安全版本函数来处理字符串,只要定义了_STDC_WANT_SECURE_LIB符号,这些_s方法都可以用,CrtDefs.h默认定义了此符号,不要取消对此符号的定义。

 

Unicode和Ansi字符串转换

 

 1  int MultiByteToWideChar( //返回
 2 
 3  UINT uCodePage, //代码页,一般可谓CP_ACP,另有CP_UTF8等
 4 
 5  DWORD dwFlags,  //标记,额外的转换控制
 6 
 7  PCSTR pMultiByteStr, //源多字节字符串
 8 
 9  int cbMultiByte,  //源多字节缓冲区字节数
10 
11  PWSTR pWideCharStr, //目的宽字符缓冲区
12 
13  int cchWiderChar //目的宽字符缓冲区字符数
14 
15 );

 

 

一定要注意的是:多字节缓冲区一般用字节表示大小,宽字符缓冲区一般用字符数表示大小。

当为cbMultiByte传入-1时,函数可以自动判断源多字节字符串的长度,当为cchWideChar传入0时,函数不进行转换,而是返回需要的宽字符个数,包括’\0’。

    

 1 LPCSTR pszSrc = "hello world";
 2 
 3 int nRet = MultiByteToWideChar(CP_ACP, 0, pszSrc, -1, NULL, 0);
 4 
 5   
 6     LPWSTR pszDst = new WCHAR[nRet];
 7 
 8     memset(pszDst, 0, nRet * sizeof(WCHAR));
 9 
10     nRet = MultiByteToWideChar(CP_ACP, 0, pszSrc, -1, pszDst, nRet);
11  
12 
13     delete[] pszDst;
14 
15     pszDst = NULL;

 

 

 

 1 int WideCharToMultiByte(
 2 
 3  UINT uCodePage, //代码页,CP_ACP,另有CP_UTF8
 4 
 5  DWORD dwFlags,  //标志,转换控制
 6 
 7  PCWSTR pWideCharStr, //源宽字符串
 8 
 9  int cchWideChar,  //源宽字符串字符个数
10 
11  PSTR pMultiByteStr, //目的多字节缓冲区
12 
13  int cbMultiByte, //目的多字节缓冲区的大小
14 
15  PCSTR pDefaultChar, //只有一个字符在uCodePage指定的代码页中没有对应的表示时,函数使用pDefaultChar指向的字符
16 
17  PBOOL bfUsedDefaultChar //如果至少有一个宽字符不能转换为对应的多字节形式,函数就会把这个变量置为TRUE,否则为FALSE,可用它来验证转换是否成功。
18 
19 );

 

 

和MultiByteToWideChar类似,如果给cchWideChar传入-1,则函数会自动判断源多字节字符串的字节数,如果给cbMultiByte传入0,则函数会返回需要的字节数,包括’\0’。

  

 1  LPCWSTR pszWideCharStr = L"hello world"; 
 2 
 3     int nRetValue = WideCharToMultiByte(CP_ACP, 0, pszWideCharStr, -1, NULL, 0, NULL, NULL);  //返回需要的字节数 
 5   
 6     PSTR pszMultiCharStr = new CHAR[nRetValue / sizeof(WCHAR)]; //这里需要特别注意! 
 8 
 9     PCSTR pDefaultChar = "C";
10 
11     BOOL bUsedDefaultChar = FALSE; 
13 
14     nRetValue = WideCharToMultiByte(
CP_ACP, 0, pszWideCharStr, -1, pszMultiCharStr, nRetValue, pDefaultChar, &bUsedDefaultChar ); 15 16 18 delete[] pszMultiCharStr; 19 20 pszMultiCharStr = NULL; 21 22 if(FALSE == bUsedDefaultChar) 24 { 25 26 //转换成功 28 } 29 30 else 32 { 34 //转换不成功 36 }

 

 

 

 

8、 判断文本是UNICODE还是ANSI

在AdvApi32.dll中导出,在WinBase.h中声明的:

BOOL IsTextUnicode(CONST PVOID pvBuffer, int cb/*字节数*/, PINT pResult);

 

INT nResult = 0;

BOOL bRet = IsTextUnicode(“hello world”, 12, &nResult);

 

 

这个函数测到的结果不一定准确,测试的字节数越多越准确。

 

 

 

 

posted on 2013-05-06 23:41  崔好好  阅读(491)  评论(0编辑  收藏  举报

导航