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);

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

  • 相关阅读:
    (转)【web前端培训之前后端的配合(中)】继续昨日的故事
    ural(Timus) 1136. Parliament
    scau Josephus Problem
    ACMICPC Live Archive 6204 Poker End Games
    uva 10391 Compound Words
    ACMICPC Live Archive 3222 Joke with Turtles
    uva 10132 File Fragmentation
    uva 270 Lining Up
    【转】各种字符串哈希函数比较
    uva 10905 Children's Game
  • 原文地址:https://www.cnblogs.com/cuish/p/3063967.html
Copyright © 2011-2022 走看看