zoukankan      html  css  js  c++  java
  • 【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);

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

  • 相关阅读:
    alkhaser学习笔记(一)Anti Debug
    8086 汇编指令手册查询(转)
    QueryUserAPC Ring3下 APC注入
    内存分配(malloc,new,VirtualAlloc,HeapAlloc,GlobalAlloc,LocalAlloc)区别与注意
    error C2220: 警告被视为错误 没有生成“object”文件 (转)
    消息队列
    分页存储过程(对有主键的表效率极高)
    asp.net vs2010 设计报表rdlc时,未能加载文件或程序集
    【翻译】创建ViewState特征的自动ViewState属性
    中英文字符的截取
  • 原文地址:https://www.cnblogs.com/cuish/p/3063967.html
Copyright © 2011-2022 走看看