一,预备知识
Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
在Unicode中:汉字“字”对应的数字是23383。在Unicode中,我们有很多方式将数字23383表示成程序中的数据,包括:UTF-8、UTF-16、UTF-32。
UTF是“UCS Transformation Format”的缩写,可以翻译成Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。
例如,“汉字”对应的数字是0x6c49和0x5b57,而编码的程序数据是:
BYTE data_utf8[] = {0xE6, 0xB1, 0x89, 0xE5, 0xAD, 0x97}; // UTF-8编码
WORD data_utf16[] = {0x6c49, 0x5b57}; // UTF-16编码
DWORD data_utf32[] = {0x6c49, 0x5b57}; // UTF-32编码
这里用BYTE、WORD、DWORD分别表示无符号8位整数,无符号16位整数和无符号32位整数。UTF-8、UTF-16、UTF-32分别以BYTE、WORD、DWORD作为编码单位。“汉字”的UTF-8编码需要6个字节。“汉字”的UTF-16编码需要两个WORD,大小是4个字节。“汉字”的UTF-32编码需要两个DWORD,大小是8个字节。根据字节序的不同,UTF-16可以被实现为UTF-16LE或UTF-16BE,UTF-32可以被实现为UTF-32LE或UTF-32BE。下面介绍UTF-8、UTF-16、UTF-32、字节序和BOM。
二,字符编码
1)ANSI 字符,一个字符一字节(8位),最多只能表达256个字符。以'\0'结尾,strlen(char)返回字符串长度
2)UTF 的全称是Unicode Transformation Format(Unicode转换格式)
1> UTF-8 将一些字符编码为1个字节,一些字符编码为2个字节,一些字符编码为3个字节,一些字符编码为4个字节
Unicode编码(16进制) UTF-8 字节流(二进制)
000000 - 00007F 0xxxxxxx
000080 - 0007FF 110xxxxx 10xxxxxx
000800 - 00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000 - 10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
2> UTF-16 将每个字符编码为两个字符(16位)Windows使用的Unicode使用UTF-16编码,因为全球各地使用的大部分语言很容易用一个16位值来表示。但是对于某些语言16位还难以表示所有字符,对于这些语言,UTF-16支持使用代理(surrogate),用32位(四字节)来表示一个字符。 UTF-16在节省空间和简化编码者两个目标之间,提供了很好的折衷。
.NET Framework始终使用UTF-16来编码所有字符和字符串,所以在开发Windows应用程序中,如果需要在本机代码(native code)和托管代码(managed code)之间传递字符或字符串,使用UTF-16能改进性能和减少内存消耗。
Java 使用UTF - 16格式
3> UTF-32 将每个字符编码为4个字节。
三,字符串数据类型
1)字符类型
char: 表示一个8位ANSI字符;
wchar_t: 表示一个16位Unicode(UTF-16)字符;
Microsoft C/C++编译器内建的数据类型,编译器只有指定了/Zc:wchar_t,才会定义这个数据类型。Visual Studio 新建一个C++项目这个编译器开关时制定的。这样才能借助于编译器天生就能理解的内建基元类型来更好的操作Unicode字符。
typedef unsigned short wchar_t; //wchar_t定义为无符号短整型;
wchar_t c = L'A'; // A 16-bit character
wchar_t szBuffer[100] = L"A String"; // An array up to 99 16-bit characters and a 16-bit terminating zero.
字符串之前加大写“L”是告诉编译器该字符串应当编译为一个Unicode字符串。
2)Windows开发团队定义了自己的数据类型。(WinNT.h)
typedef char CHAR; // An 8-bit character
typedef wchar_t WCHAR; // A 16-bit character
//Pointer to 8-bit character(s)
typedef CHAR *PCHAR;
typedef CHAR *PSTR;
typedef CONST CHAR *PCSTR
//Pointer to 16-bit character(s)
typedef WCHAR *PWCHAR;
typedef WCHAR *PWSTR;
typedef CONST WCHAR *PCWSTR;
#ifdef UNICODE //使用宽字符 16bit
typedef WCHAR TCHAR, *PTCHAR, PTSTR;
typedef CONST WCHAR *PCTSTR;
#define __TEXT(quote) quote // r_winnt
#define __TEXT(quote) L##quote
#else
typedef CHAR TCHAR, *PTCHAR, PTSTR;
typedef CONST CHAR *PCTSTR;
#define __TEXT(quote) quote
#endif
#define TEXT(quote) __TEXT(quote)
利用以上类型和宏来写代码,无论使用ANSI还是Unicode字符,它都能通过编译。如下:
//如果UNICODE被定义, 则为16位字符; 否则为8位字符
TCHAR c = TEXT('A');
TCHAR szBuffer[100] = TEXT("A String");
四,Windows中的Unicode 函数 和ANSI 函数
1)自Windows NT起,Windows的所有版本都完全用Unicode构建 。也就是说,所有核心函数(创建窗口,显示文本,进行字符串处理等等)都需要Unicode字符串。调用Windows函数时,
如果向它传入一个ANSI字符串,那么函数首先会把字符串转换为Unicode,再把结果传给操作系统。如果希望函数返回ANSI字符串,那么操作系统会先把Unicode字符串转换为ANSI字符串,
再把结果返回给我们的应用程序。为了执行这些字符串转换,系统会产生时间和内存上的开销。
2)代码片段
#ifdef UNICODE
#define CreateWindowEx CreateWindowExW
#else
#define CreateWindowEx CreateWindowExA
#endif
Visual Studio创建一个新项目时默认会定义UNICODE,所以调用 CreateWindowExW ,即Unicode版函数。ANSI版函数 CreateWindowExA ,只是一个转换层,它负责分配内存,以便将ANSI字符串转换为Unicode字符串,然后再调用 Unicode版函数 CreateWindowExW。所以为了减少时间和内存上的 耗费,使用Unicode来开发程序。
所有需要字符串作为参数的COM接口方法都只接受Unicode字符串。一些函数存在的目的是为了向前兼容16位的Windows程序
五,C运行库中的Unicode和ANSI
为了使源代码既能用ANSI编译,又能用Unicode编译TChar.h定义了一下宏:
#ifdef _UNICODE
#define _tcslen wcslen
#else
#define _tcslen strlen
#endif
在应用程序中应确保同时定义了UNICODE和_UNICODE 要么什么都不定义
六,为何要用Unicode
1)使用Unicode,有利于应用程序的本地化;
2)使用Unicode,只需发布一个二进制(.exe或.dll)文件,即可支持所有语言;
3)使用Unicode,提升应用程序的效率,代码执行更快,占用内存更少。Windows内部的一切工作都是使用 Unicode字符和 Unicode字符串来进行的。假如我们坚持使用ANSI字符或字符串,Windows将被迫分配内存,将字符或字符串转换为Unicode形式;
4)使用 Unicode,应用程序能够轻松的调用尚未弃用的Windows函数,因为一些Windows函数提供的版本只能处理 Unicode字符和字符串;
5)使用 Unicode,应用程序的代码很容易与COM集成(要求使用 Unicode );
6)使用 Unicode,应用程序的代码很容易与.NET Framework集成(要求使用 Unicode );
7)使用 Unicode,能保证应用程序的代码能够轻松操纵我们自己的资源(其中的字符串总是 Unicode形式 )。
七,C运行库中的安全字符串函数
1)任何字符串修改函数都存在一个安全隐患,如果目标字符串缓存区不够大,无法容纳所生成的字符串,就会导致内存中数据被破坏。
WCHAR szBuffer[3] =L"";
wcscpy(szBuffer,L"abc"); //没有给'\0'留下空间
缺陷:没有指定缓存区最大长度的参数,函数不知道自己会破坏内存。
2)C中的所有字符串处理函数,都对应一个新版本的函数,前面名称相同,但是最后添加了一个后缀 _s (代表secure)。如:
PTSTR _tcscpy (PTSTR strDestination, PCTSTR strSource ) ; //这是C运行库现有的字符串函数
errno_t _tcscpy_s(PTSTR strDestination, size_t numberOfCharacters ,PCTSTR strSource ) ;//新增安全函数
还有memcpy_s,memmove_s,wmencpy,wmemove_s等函数
这类函数检查的项目有
1、指针不为空
2、整数在有效范围内
3、枚举值是有效的
4、缓冲区足以容纳结果数据
如果这些检查中有一项失败,函数就会设置局部与线程的C运行时变量errno,然后返回一个errno_t值来指出成功或是失败。然而,这些函数并不实际返回。如果是debug build,则会调用 Debug Assertion Failed,然后终止应用程序(即利用断言),如果是release build,程序自动终止。
八,有更多控制力的处理函数
在Microsoft的StrSafe.h文件中定义新的安全字符串函数,除了新的安全字符串函数,还新增了一些函数,用于在执行字符串处理时提供更多的控制。可以控制填充符,或者指定如何进行截断
下面为部分原型:常用的
HRESULT StringCchCat( PTSTR pszDest, size_t cchDest, PCTSTR pszSrc) ;
HRESULT StringCchCatEx( PTSTR pszDest, size_t cchDest, PCTSTR pszSrc,
PTSTR* ppszDestEnd, size_t *pcchRemaining, DWORD dwFlags );
还有 StringCchCpy
StringCchCpyEx
StringCchPrintf
StringCchPrintfEx等
可以看出 含有“Cch”,这表示count of characters即字符数,通常用_countof宏来获取。
还有含有“Cb”,如StringCbCat(Ex),StringCbCopy(Ex),StringCbPrintf(Ex) 这些函数指定的是字节数,通常使用sizeof操作符来获取此值。这些函数都返回HRESULT不同于安全函数(带_S后缀的),当缓冲区太小的时候,这些函数会执行截断
九、windows字符串函数
windows也提供了各种字符串处理函数。
例如我们经常比较字符串以进行相等性测试或是排序,为此,最理想的函数是
CompareString(Ex)和 CompareStringOrdinal
第一个函数功能大用法比较复杂
CompareString(
LCID locale, //语言标识
DWORD dwCmdFlags,//比较时,使用的标识。比如NORM_IGNORECASE 忽略大小写
PCTSTR pString1,// 第一个字符串
int cch1, //字符个数
PCTSTR pString2,
int cch2)
注:只支持unicode
#include <iostream> #include <Windows.h> #include <strsafe.h> using namespace std; int main(int argc, char* argv[]) { int ret = 0; LCID lc = GetThreadLocale(); // PCTSTR ps1 = L"12345"; // PCTSTR ps2 = L"1234556"; TCHAR ps1[10] = L"12345"; TCHAR ps2[10] = L"12345"; ret = CompareString(lc, SORT_STRINGSORT, ps1, _countof(ps1), ps2, _countof(ps2)); cout << ret << endl; getchar(); return 0; }
十,字符和字符串处理方式
1)始终使用安全的字符串处理函数,比如后缀为 _s 的函数,或者前缀为 StringCch 的函数。后者主要在我们想明确控制截断的时候使用了如果不像明确控制截断,首选前者。
2)利用 /GS 和 /RTCs 编译器标志来自动检测缓冲区溢出。
3)不要使用 Kernel32 方法来进行字符串处理,比如 lstrcat 和 lstrcpy 。
4)在应用程序的代码中,需要比较两种字符串,应使用 CompareStringOrdinal 来进行比较。
5)用通用的数据类型表示文本字符,TCHAR PTSTR
6)用TEXT()或 _T 表示字面量字符和字符串
7)避免使用printf()系列函数,尤其不要使用%s和%S类型字段进行ANSI与Unicode字符串之间的类型转换
十一,Unicode与ANSI字符串转换
int MultiByteToWideChar( //将多字节字符串转换为宽字节字符串
UINT uCodePage , // code page
DWORD dwFlags , // character-type options
PCSTR pMultiByteStr , // string to map
int cbMultiByte , // number of bytes in string
PWSTR pWideCharStr , // wide-character buffer
int cchWideChar // size of buffer
);
一般按照以下步骤将一个多字节字符串转化为Unicode形式
调用 MultiByteToWideChar,为 pWideCharStr 传入 NULL,为cchWideChar参数传入0,为cbMultiByte参数传入-1;
分配一块足以容纳转换后的Unicode字符串的内存。它的大小是一个 MultiByteToWideChar 调用的返回值乘以sizeof(wchar_t);
再次调用 MultiByteToWideChar ,这一次将缓冲区地址作为pWideCharStr参数的值传入,将第一次 MultiByteToWideChar 调用的返回值乘以sizeof(wchar_t)后得到的大小作为cchWideChar参数的值传入;
使用转换后的字符串;
释放Unicode字符串占用的内存块。