【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);
这个函数测到的结果不一定准确,测试的字节数越多越准确。