天天看点

宽字符与窄字符的处理

1.      《windows核心编程》

>Unicode是传递字符串的最佳手段。

2.      C标准库中与wchar_t相关的文件:

《windows核心编程》

1)      文件”string.h”定义了wchar_t数据类型:

typedef unsignedshort wchar_t;
           

2)      标准的ANSI C函数和它们等价的UNICODE函数:

>所有的U n i c o d e 函数均以w c s 开头,w c s 是宽字符串的英文缩写。若要调用Un i c o d e函数,只需用前缀wc s 来取代AN S I 字符串函数的前缀st r 即可。

>若要建立双重功能,必须包含T C h a r. h 文件,而不是包含S t r i ng . h 文件。

“双重功能”,即为定义的宏,在UNICODE下为哪种使用,在ANSI下又为哪种定义。

3)      文件”tchar.h”定义了TCHAR类型

> T C h a r. h 文件的唯一作用是帮助创建A N S I / U n i c o d e 通用源代码文件。它包含你应该用在源代码中的一组宏,而不应该直接调用st r 函数或者wc s 函数。如果在编译源代码文件时定义了UN I C O D E ,这些宏就会引用wc s 这组函数。如果没有定义_U N I C O D E ,那么这些宏将引用st r这组宏。

>例如,在T C h a r. h 中有一个宏称为_ t c s cp y 。如果在包含该头文件时没有定义_U N I C O D E ,那么_t c s c p y 就会扩展为AN S I 的st r c p y 函数。但是如果定义了_UNICODE,_tcscpy 将扩展为Un i c o d e的wc s c p y 函数。拥有字符串参数的所有C运行期函数都在TC h a r. h 文件中定义了一个通用宏。如果使用通用宏,而不是AN S I / U n i c o d e 的特定函数名,就能够顺利地创建可以为A N S I 或U n i c od e进行编译的源代码。

4)      _UNICODE和UNICODE

>_ U N I C O D E 宏用于C 运行期头文件,而U N I C OD E 宏则用于Win d o w s 头文件。当编译源代码模块时,通常必须同时定义这两个宏。

3.      windows中与wchar_t相关的文件

《windows核心编程》

1)      定义了UNICODE数据类型的文件:

i.                    文件”wtypes.h”定义了windows所有应用的数据类型,包括字符串类型。

#TODO(continue): 既然是定义了所有应用的数据类型,那么应该是不用手动添加的。待测。。

ii.                  文件”winnt.h”文件

winnt.h文件编译问题,解决方案:

A.     http://hagejid.blog.51cto.com/141754/294797

调准包含目录的次序

B.     http://sz3mhx.573114.com/Blog/Html/8E16/118149.html

在include <winnt.h>前include <windows.h>

2)      windows中的UNICODE函数和ANSI函数

i.                    windows中的函数名都是大写开头的,windows中的函数类型都是大写开头的,比如整型为UINT, INT, LONG。

ii.                  windows中许多函数都是”兼容”UNICODE和ANSI字符串。比如CreateWindowEx,它实际上有两种形式:CreateWindowExA和CreateWindowExW。A后缀的表示其中的字符串参数采用的ANSI型字符串,W后缀的表示其中的字符串参数采用的是宽字符型字符串。

iii.                 操作系统字符串函数名形如:StrCat,StrCpy, StrCmp等。与C运行期字符串函数相似。使用这些函数须加上”shlwapi.h”头文件。

>若要使用这些函数,必须加上S h l WA p i . h 头文件。另外,如前所述,这些字符串函数既有AN S I 版本,也有Un i c o d e 版本,例如St r C a t A 和St r C a t W 。

iv.                 windows另外提供了一组对unicode和ANSI字符串的操作函数:(这些函数是作为宏实现的,如lstrcat是lstrcatA和lstrcatW宏定义)。使用这些函数须加上”winbase.h”头文件。

lstrcat, lstrcpy, lstrlen,

lstrcmp(对两个字符串进行区分大小写的比较),

lstrcmpi(对两个字符串进行不区分大小写的比较)

v.                  转换大小写的函数:使用这些函数须加上”winuser.h”头文件。

PTSTR CharLower(PTSTRpszString);
PTSTR CharUpper(PTSTRpszString);
           

>其他的C 运行期函数,如i s a l ph a 、is l o w e r 和is u p p e r ,返回一个值,指明某个字符是字母字符、小写字母还是大写字母。WindowsAPI 提供了一些函数,也能返回这些信息:。使用这些函数须加上”winuser.h”头文件。

BOOL IsCharAlpha(TCHARch);
BOOLIsCharAlphaNumerica(TCHAR ch);
BOOL IsCharUpper(TCHARch);
BOOL IsCharLower(TCHARch);
           

vi.                 printf函数:sprintf(C标准库提供的函数)和swprintf(操作系统提供的函数即windows函数)

A.    sprintf的使用:在头文件”stdio.h”中

char szA[100];
sprintf(szA, “%s”,“asdf”);
sprintf(szA, ”%S”, L”ASDF”);// 在将宽字符转换为窄字符的时候s须大写
           

B.    swprintf的使用:在头文件”stdio.h”或”wchar.h”中

wchar_t szW[100];
swprintf(szW, L”%s”,L”SADF”);
swprintf(szW, L”%S”, “SADF”);// 在将窄字符转换为宽字符的时候s须大写
           

vii.               sprintf与sprintf_s,sprint_s_l,swprintf与swprintf_s,swprint_s_l的区别:

http://technet.microsoft.com/zh-cn/ce3zzk1k(de-de).aspx

>    sprintf_s 函数在 buffer设置格式并存储一系列字符和值。 每 argument (如果有) 基于在 format相应的格式规范转换和输出。 该格式包括普通字符并具有窗体和功能和printf的 format 参数相同。 null 字符从右向左书写的最后一个字符之后追加。如果复制出现在重叠的字符串之间,该行为不确定。

sprintf_s 和 sprintf 之间的主要差异是 sprintf_s 检查格式字符串的格式无效字符,sprintf ,而只检查格式字符串或缓冲区是否 NULL 指针。如果任何检查失败,无效参数调用处理程序,如 参数验证所述。如果执行允许继续,该函数返回 -1 并将 errno 到 EINVAL。

sprintf_s 和 sprintf 之间的另一个主要区别在于 sprintf_s 带有指定输出区域的大小长度参数在字符。 如果缓冲区为因此打印的文本太小缓冲区设置为空字符串,而无效参数调用处理程序。不同 snprintf, sprintf_s 确保缓冲区将 null 终止 (除非缓冲区大小为零)。

swprintf_s 是 sprintf_s的宽字符版本;为 swprintf_s 的指针参数是宽字符字符串。 编码错误的检测到 swprintf_s 的可能与在 sprintf_s。这些功能的版本与 _l 后缀的相同,只不过它们使用区域设置参数而不是当前线程区域设置。

在 C++ 中,使用这些功能由模板重载简化;重载可推断缓冲区长度 (自动不再需要指定范围参数),并且还可以用以较新,安全重复自动替换旧,不安全的功能。有关更多信息,请参见 安全模板重载。

具有提供对 sprintf_s 的版本时所发生的其他控件,如果缓冲区太小。 有关更多信息,请参见_snprintf_s,_snprintf_s_l,_snwprintf_s,_snwprintf_s_l。

3)      UNICODE与ANSI字符串之间的转换

i.                    windows函数:

A.       int MultiByteToWideChar

int MultiByteToWideChar(
    UINT CodePage,          //code page
    DWORD dwFlags,          //character-type options
    LPCSTR lpMultiByteStr,  //address of string to map
    int cchMultiByte,       //number of bytes in string
    LPWSTR lpWideCharStr,   //address of wide-character buffer
    int cchWideChar         //size of buffer
);
           

> u C o d e P a ge 参数用于标识一个与多字节字符串相关的代码页号。d w F l a g s 参数用于设定另一个控件,它可以用重音符号之类的区分标记来影响字符。这些标志通常并不使用,在d w F l a g s参数中传递0 。p M u l t i B y t e S t r 参数用于设定要转换的字符串,c c h M u l t i B y t e 参数用于指明该字符串的长度(按字节计算)。如果为c c h M u l t i B y t e 参数传递- 1 ,那么该函数用于确定源字符串的长度。

转换后产生的U n i c o d e 版本字符串将被写入内存中的缓存,其地址由p Wi d e C h a r S t r 参数指定。必须在c c h Wi d e C h a r 参数中设定该缓存的最大值(以字符为计量单位)。如果调用M u l t i B y t e To Wi d e C h a r ,给c c h Wi d e C h a r 参数传递0 ,那么该参数将不执行字符串的转换,而是返回为使转换取得成功所需要的缓存的值。

n  用例:

MultiByteToWideChar(CP_ACP, 0, pMulti8yteStr, -1, 
      pWideCharStr, nLenOfWideCharStr);
           

B.       int WideCharToMultiByte

int WideCharToMultiByte(
  UINT CodePage,         // code page
  DWORD dwFlags,         // performance and mapping flags
  LPCWSTR lpWideCharStr, // address of wide-character string
  int cchWideChar,       // number of characters in string
  LPSTR lpMultiByteStr,  // address of buffer for new string
  int cchMultiByte,      // size of buffer
  LPCSTR lpDefaultChar,  // address of default for unmappable 
                         // characters
  LPBOOL lpUsedDefaultChar   // address of flag set when default 
                             // char. used
);
           

>该函数与M u l t i B i t e To Wi d e C h a r 函数相似。同样,u C o d e P a g e 参数用于标识与新转换的字符串相关的代码页。d w F l a g s 则设定用于转换的其他控件。这些标志能够作用于带有区分符号的字符和系统不能转换的字符。通常不需要为字符串的转换而拥有这种程度的控制手段,你将为d w F l a g s 参数传递0 。

p Wi d e C h a r S tr 参数用于设定要转换的字符串的内存地址,c c h Wi d e C h a r 参数用于指明该字符串的长度(用字符数来计量)。如果你为c c h Wi d e C h a r 参数传递- 1 ,那么该函数用于确定源字符串的长度。

转换产生的多字节版本的字符串被写入由p M u l t i B y t e S t r 参数指明的缓存。必须在c c h M u l t i B y t e参数中设定该缓存的最大值(用字节来计量)。如果传递0 作为Wi d e C h a r To M ul t i B y t e 函数的c c h M u l t i B y te 参数,那么该函数将返回目标缓存需要的大小值。通常可以使用将多字节字符串转换成宽字节字符串时介绍的一系列类似的事件,将宽字节字符串转换成多字节字符串。

你会发现,Wi d e C h a r To M u l t i B y t e 函数接受的参数比M u l t i B y t e To Wi d e C h a r 函数要多2 个,即p D e f a u l t C h ar 和p f U s e d D e f a u l t C h a r 。只有当Wi d e C h a r To M u l t i B y t e 函数遇到一个宽字节字符,而该字符在u C o d e P a g e 参数标识的代码页中并没有它的表示法时,Wi d e C h a r To M u l t i B y t e 函数才使用这两个参数。如果宽字节字符不能被转换,该函数便使用p D e f a u l t C h a r 参数指向的字符。如果该参数是N U L L (这是大多数情况下的参数值),那么该函数使用系统的默认字符。该默认字符通常是个问号。这对于文件名来说是危险的,因为问号是个通配符。

p f U s e d D e f a ul t C h a r 参数指向一个布尔变量,如果宽字符串中至少有一个字符不能转换成等价多字节字符,那么函数就将该变量置为T R U E 。如果所有字符均被成功地转换,那么该函数就将该变量置为FA L S E 。当函数返回以便检查宽字节字符串是否被成功地转换后,可以测试该变量。同样,通常为该测试传递N U L L 。

n  用例:

WideCharToMultiByte(CP_ACP, 0, pWideCharStr, -1, 
         pMultiByteStr, strlen(pMultiByteStr), NULL, NULL);
           

#Q: strlen(pMultiByteStr)能取到此数组的大小吗?

4.      先从UNICODE和ANSI的基础开始讲起

1)      一个ANSI字符占一个字节共8位,一个UNICODE字符占两个字节共16位;ANSI字符串以’\0’结束,0x00。

#Q: UNICODE字符串以什么结束??

#A: UNICODE字符串以L”\0”结束,0x0000。

5.      UNICODE和ANSI字符的相关定义及应用在各种运行库中的体现如下:

1)      在C标准库中

i.                    UNICODE在C标准库下编译的宏定义为_UNICODE

ii.                  宽字符的数据类型为wchar_t,窄字符的数据类型为char。数据类型的定义在头文件string.h中,对wchar_t的定义为:

typedef unsigned short wchar_t;
           

iii.                 在头文件string.h中,定义了分别对宽字符和窄字符的操作函数,如wcscpy和strcpy等。对宽字符的操作函数和对窄字符的操作函数的对照见:

http://blog.csdn.net/typecool/article/details/5877458

宽字符与窄字符的处理

iv.                 而在头文件tchar.h中,定义了tchar宏,该文件模块功能实现了”双重功能”。

即例如_tcscpy方法宏,当编译环境定义了UNICODE宏,则此方法宏为wcscpy,不然则为strcpy。等等。。。。

v.                  #Q: 对于对宽字符和窄字符的处理函数带后缀_s,如wcscpy_s,strcpy_s,此类方法与wcscpy和strcpy等的区别在哪里?

#A: http://technet.microsoft.com/zh-cn/subscriptions/td1esda9(v=vs.80).aspx

wcscpy_s方法定义如下:

errno_t wcscpy_s(
  wchar_t *strDestination,
  size_t sizeInWords,
  const wchar_t *strSource
);
           

wcscpy方法定义如下:

wchar_t* wcscpy_s(
  wchar_t *strDestination,
  const wchar_t *strSource
);
           

wcscpy_s方法多了一项参数size_tsizeInWords,即要拷贝的字符的个数。这样就可避免strDestination空间分配不足,如此使用更安全!

2)      在windows中

i.                    UNICODE在windows下编译的宏定义为UNICODE

ii.                  windows下定义的数据类型基本上都采用的是全大写的,比如整型INT,无符号整型UINT,wchar_t定义为WCHAR,char定义为CHAR。

iii.                 #Q: 头文件wtypes.h windef.h winnt.h这三个文件该怎么用???有好多类型都定义串了,这三个文件都是定义在同一级目录下面。

#A: http://baike.baidu.com/view/1586331.htm

>WINDEF.H 基本数据类型定义。

WINNT.H 支持Unicode的类型定义。

WINBASE.H Kernel(内核)函数。

WINUSER.H 用户界面函数。

WINGDI.H 图形设备接口函数。

>这些头文件定义了Windows的所有资料型态、函数调用、资料结构和常数识别字,它们是Windows文件中的一个重要部分。

#Q: 头文件windows.h的用法?

#A: http://baike.baidu.com/view/1586331.htm

>WINDOWS.H是一个最重要的头文件,它包含了其他Windows头文件,这些头文件的某些也包含了其他头文件。

#Q: 头文件windows.h可否直接加载???

#A: 可以。

所以,使用windows最简单最方便的办法就是直接加载头文件windows.h即可:#include <Windows.h>

MFC/Windows基本数据类型

http://blog.programfan.com/article.asp?id=33809

iv.                 windows中处理UNICODE和ANSI的函数有

A.     定义的宏函数,与C标准库形似的有:

StrCat,StrCpy等等。

a)      在头文件shlwapi.h中如下定义:

#ifdef UNICODE
#define StrCat                  StrCatW
#define StrCmp                  StrCmpW
#define StrCmpI                 StrCmpIW
#define StrCpy                  StrCpyW
#define StrCpyN                 StrCpyNW
#define StrCatBuff              StrCatBuffW
#else
#define StrCat                  lstrcatA
#define StrCmp                  lstrcmpA
#define StrCmpI                 lstrcmpiA
#define StrCpy                  lstrcpyA
#define StrCpyN                 lstrcpynA
#define StrCatBuff              StrCatBuffA
#endif
           

b)      在头文件winbase.h中定义了以下宏函数:

lstrcat,lstrcpy, lstrlen,
lstrcmp(对两个字符串进行区分大小写的比较),
lstrcmpi(对两个字符串进行不区分大小写的比较)
           

c)      在头文件winuser.h中:

PTSTRCharLower(PTSTR pszString);
PTSTRCharUpper(PTSTR pszString);
           

>其他的C 运行期函数,如i s a l ph a 、is l o w e r 和is u p p e r ,返回一个值,指明某个字符是字母字符、小写字母还是大写字母。WindowsAPI 提供了一些函数,也能返回这些信息:

BOOLIsCharAlpha(TCHAR ch);
BOOLIsCharAlphaNumerica(TCHAR ch);
BOOLIsCharUpper(TCHAR ch);
BOOLIsCharLower(TCHAR ch);
           

windows中对UNICODE和ANSI的处理函数都采用了宏定义,即这样里面用到的参数就是类型就不确定是UNICODE还是ANSI,完全是根据编译环境是否定义了UNICODE/_UNICODE来定。所以参数类型最好采用TCHAR类型,或者是在调用windows方法时进行宏判断:

#ifdef UNICODE
     WINFUNC(WCHAR)
#else
     WINFUNC(CHAR)
#end
           

3)      在C++标准库中

这里只讲std::string和std::wstring。

std::string

http://www.cplusplus.com/reference/string/string/

std::wstring

http://www.cplusplus.com/reference/string/wstring/

4)      在MFC中

在MFC中队UNICODE和ANSI的封装使用就是CString了,CString也为一个宏类,即双重类。

>一个CString对象由可变长度的一队字符组成。CString使用类似于Basic的语法提供函数和操作符。连接和比较操作符以及简化的内存管理使CString对象比普通字符串数组容易使用。

CString是基于TCHAR数据类型的对象。如果在你的程序中定义了符号_UNICODE,则TCHAR被定义为类型wchar_t,即16位字符类型;否则,TCHAR被定义为char,即8位字符类型。在UNICODE方式下,CString对象由16位字符组成。非UNICODE方式下,CString对象由8位字符组成。

当不使用_UNICODE时,CString是多字节字符集(MBCS,也被认为是双字节字符集,DBCS)。注意,对于MBCS字符串,CString仍然基于8位字符来计算,返回,以及处理字符串,并且你的应用程序必须自己解释MBCS的开始和结束字节。

6.      讲完了在各个运行环境下对UNICODE和ANSI字符串的操作,现在涉及到的也就是重点中的重点,UNICODE和ANSI字符串之间的转换操作!

1)      wcstombs和wcstombs_s,mbstowcs和mbstowcs_s

定义在头文件stdlib.h中

2)      sprintf和sprintf_s, sprintf_l_s,swprintf和swprintf_s, swprintf_l_s

定义在头文件stdio.h中

3)      MultiByteToWideChar,WideCharToMultiByte

定义在头文件winnls.h中

7.      C标准库、windows、MFC的区别:

n  C标准库:数据类型、方法均为小写的,未封装

<tchar.h> <string.h>

_tcs**   wcs**   str**

n  windows:数据类型大写,方法首字母大写,未封装

<winbase.h> <shlwapi.h> <windows.h>

Str**    lstr**

n  MFC:数据类型和方法均封装起来了,类以C开头

<afxwin.h>

MFC是对windows的封装。

CString

8.      C标准库中宽字符与窄字符之间的转换

windows核心编程中文版.chm

2.9.1 Windows字符串函数

2.9.4 在Unicode和ANSI之间转换

1)        sprintf && sprintf_s

sprintf定义:

/**
 * @param [out] _Dest 输出的目的地址
 * @param [in] _Format 输出格式
 * @param [in] ... 参数列表
 * @retval 返回输出字符的个数
 */
int __cdecl sprintf(char* _Dest,const char* _Format, ...);
           

sprintf_s定义:

/**
 * @param [out] _DstBuf 输出的目的地址
 * @param [in] _SizeInBytes 限制按照输出格式_Format转换后的字符个数,同时也限制为_DstBuf分配的缓存空间
 * @param [in] _Format 输出格式
 * @param [in] ... 参数列表
 * @retval 返回输出字符的个数
 */
int __cdecl sprintf_s(char* _DstBuf,size_t _SizeInBytes, const char* _Format, ...);
           

函数sprintf和sprintf_s的区别:sprintf_s函数多了一个对目的地址分配的空间和转换字符的个数的限制。例:

ASSERT((sizeof(tranStr)/sizeof(char))<= _SizeInBytes);
ASSERT((sizeof(_DstBuf)/sizeof(char))>= _SizeInBytes);
           

首先判断的是按照输出格式转换的字符串tranStr的个数是否小于等于SizeInBytes,若否,则弹出断言:Bufferto small。可见,在根据_Format进行字符转换时所分配的临时存储空间是由_SizeInBytes来指定的。然后对输出的目的地址_DstBuf的空间大小进行判断,判断其大小是否大于等于_SizeInButes,若否,则可能会出现误用非法空间的可能。不过sprintf_s都为其做了安全处理。所以总的来说,用sprintf_s比用sprintf更安全。

sprintf在对宽窄字符之间的转换的用法在于:

转换窄字符的用法:

char_Dest[100];
sprintf_s(_Dest, 100, "%s","ANSII STR");
           

其中转换格式必须是小写s;

转换宽字符的用法:

sprintf_s(_Dest, 100, "%S",L"UNICODE STR");
           

其中转换格式必须是大写S;对比例子:

sprintf_s(_Dest, 100, "%s_%S", "ANSII STR",L"UNICODE STR");
           

同样,swprintf和swprintf_s的用法:

wchar_t _Dest[100];
           swprintf_s(_Dest,100, L”%s_%S”, L”UNICODE STR”, “ANSII STR”);
           

2)        swprintf && swprintf_s

3)        wcstombs && wcstombs_s

wcstombs的定义:

/**
 * @param [in] _MaxCount 限制取出_Source中字符的个数
 * @retval 返回转换的字符的个数,包括对结束符的计算
 */
size_t __cdecl wcstombs(char* _Dest,const wchar_t* _Source, size_t _MaxCount);
           

wcstombs_s的定义:

/**
 * @param [out] _PtNumOfCharConverted 返回转换的字符的个数,包括对结束符的计算
 * @param [out] _Dst 转换字符存放的目的地址
 * @param [in] _DstSizeInBytes 限制取出_Src中字符的个数,同时也限制为_Dst分配的缓存空间
 * @param [in] _Src 要转换的字符
 * @param [in] _MaxCountInBytes 限制取出_Src中字符的个数
 * @retval 返回错误号,为0则表示转换成功
 */
errno_t__cdecl wcstombs_s(size_t* _PtNumOfCharConverted, char* _Dst, size_t_DstSizeInBytes,  const wchar_t* _Src,size_t _MaxCountInBytes);
           

函数wcstombs_s中参数_DstSizeInBytes起到的作用:对目的地址_Dst分配的缓存空间的限制,若sizeof(_Dst)/sizeof(char) < _DstSizeInBytes,则会提示异常;同时也限制从_Src取出的个数,至于确定取_Src中字符的个数,先是选择_DstSizeInBytes和_MaxCountInBytes中较小的值minNum,然后如果minNum小于wcslen(_Src),则从中取出minNum个字符,再判断_DstSizeInBytes < (minNum + 1),为true则出断言。

所以一般的形式为这样:

wcstombs_s(_PtNumOfCharConverted,_Dst, wcslen(_Src) + 1, _Src, _TRUNCATE);
           mbstowcs_s(_PtNumOfCharConverted,_Dst, strlen(_Src) + 1, _Src, _TRUNCATE);
           

wcstombs_s、wcstombs和mbstowcs_s、mbstowcs都是调用的WideCharToMultiByte和MultiByteToWideChar。这里是否可以看成是对WideCharToMultiByte和MultiByteToWideChar的封装使用?

4)        mbstowcs && wcstombs_s

9.      windows中宽字符与窄字符之间的转换

WideCharToMultiByte

MultiByteToWideChar

10.  MFC中宽字符与窄字符之间的转换

MFC中跟字符串相关的有:

CString, CStringList, CStringArray。

http://blog.csdn.net/pizi0475/article/details/5346708

目前这些知识暂时够用,已达到解惑的程度,暂结贴!若以后需要继续深入在开贴。