1. 在Project->C/C++->Preprocessor definitions 中去除_MBCS, 增加_UNICODE
2. 在Project->Link->Category->Output->Entry-point symbol处增加wWinMainCRTStartup
3. 拷贝3个文件到工程所在目录:mfc42u.lib, mfcs42u.lib, atlthunk.lib (WINDDK中就有,或者直接指定这三个文件的路径, 否则会link失败)
4. 数据类型 说明
a) WCHAR Unicode字符
b) PWSTR 指向Unicode字符串的指针
c) PCWSTR 指向一个恒定的Unicode字符串的指针
d) 对应的ANSI数据类型为CHAR,LPSTR和LPCSTR
e) ANSI/Unicode通用数据类型为TCHAR,PTSTR,LPCTSTR
5. 操作说明
a) ANSI 操作函数以str开头 strcpy
b) Unicode 操作函数以wcs开头 wcscpy
c) MBCS 操作函数以_mbs开头 _mbscpy
d) ANSI/Unicode 操作函数以_tcs开头 _tcscpy(C运行期库),如_tcscpy,_tcscat, _tcscmp, _tcschr (查找字串),
e) ANSI/Unicode 操作函数以lstr开头 lstrcpy(Windows函数)
6. 字符集说明
a) Unicode L“string”
b) ANSI “string”
c) ANSI/Unicode T(“string”)
或_TEXT(“string”)if(szError[0]==_TEXT(‘J’) ){ }
7. UNICODE 转换成ANSI
unsigned short * unicode;
int size= WideCharToMultiByte(CP_ACP,0,unicode,-1, NULL, 0, NULL,0);
char * ansi=new char[size];
WideCharToMultiByte(CP_ACP,0,unicode,-1, ansi, NULL, 0);
8. ANSI 转换成UNICODE
CString ansi;
DWORD dwNum;
dwNum = MultiByteToWideChar(CP_ACP, 0, m_ANSI, -1, NULL, 0);
wchar_t *pwText = new wchar_t[dwNum];;
MultiByteToWideChar(CP_ACP, 0, m_ANSI, -1, pwText, dwNum);
9. 判断如果文本文件的开头两个字节是0xFF和0xFE,那幺就是Unicode,否则是ANSI
================================================
使用Unicode编码可以使您的工程同时支持多种语言,使您的工程国际化。
另外,Windows NT是使用Unicode进行开发的,整个系统都是基于Unicode的。如果调用一个API函数并给它传递一个ANSI(ASCII字符集以及由此派生并兼容的字符集,如:GB2312,通常称为ANSI字符集)字符串,那么系统首先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。如果希望函数返回ANSI字符串,系统就会首先将Unicode字符串转换成ANSI字符串,然后将结果返回给您的应用程序。进行这些字符串的转换需要占用系统的时间和内存。如果用Unicode来开发应用程序,就能够使您的应用程序更加有效地运行。
下面例举几个字符的编码以简单演示ANSI和Unicode的区别:
字符
A
N
和
ANSI码
41H
4eH
cdbaH
Unicode码
0041H
004eH
548cH
三、使用C++进行Unicode编程
对宽字符的支持其实是ANSI C标准的一部分,用以支持多字节表示一个字符。宽字符和Unicode并不完全等同,Unicode只是宽字符的一种编码方式。
1、宽字符的定义
在ANSI中,一个字符(char)的长度为一个字节(Byte)。使用Unicode时,一个字符占据一个字,C++在wchar.h头文件中定义了最基本的宽字符类型wchar_t:
1.
typedef
unsigned
short
wchar_t
;
从这里我们可以清楚地看到,所谓的宽字符就是无符号短整数。
2、常量宽字符串
对C++程序员而言,构造字符串常量是一项经常性的工作。那么,如何构造宽字符字符串常量呢?很简单,只要在字符串常量前加上一个大写的L就可以了,比如:
1.
wchar_t
*str1=L
" Hello"
;
这个L非常重要,只有带上它,编译器才知道你要将字符串存成一个字符一个字。还要注意,在L和字符串之间不能有空格。
3、宽字符串库函数
为了操作宽字符串,C++专门定义了一套函数,比如求宽字符串长度的函数是
1.
size_t
__cdel wchlen(
const
wchar_t
*);
为什么要专门定义这些函数呢?最根本的原因是,ANSI下的字符串都是以’\0’来标识字符串尾的(Unicode字符串以“\0\0”结束),许多字符串函数的正确操作均是以此为基础进行。而我们知道,在宽字符的情况下,一个字符在内存中要占据一个字的空间,这就会使操作ANSI字符的字符串函数无法正确操作。以”Hello”字符串为例,在宽字符下,它的五个字符是:
0x0048 0x0065 0x006c 0x006c 0x006f
在内存中,实际的排列是:
1.
48 00 65 00 6c 00 6c 00 6f 00
于是,ANSI字符串函数,如strlen,在碰到第一个48后的00时,就会认为字符串到尾了,用strlen对宽字符串求长度的结果就永远会是1!
4、用宏实现对ANSI和Unicode通用的编程
可见,C++有一整套的数据类型和函数实现Unicode编程,也就是说,您完全可以使用C++实现Unicode编程。
如果我们想要我们的程序有两个版本:ANSI版本和Unicode版本。当然,编写两套代码分别实现ANSI版本和Unicode版本完全是行得通的。但是,针对ANSI字符和Unicode字符维护两套代码是非常麻烦的事情。为了减轻编程的负担,C++定义了一系列的宏,帮助您实现对ANSI和Unicode的通用编程。
C++宏实现ANSI和Unicode的通用编程的本质是根据”_UNICODE”(注意,有下划线)定义与否,这些宏展开为ANSI或Unicode字符(字符串)。
如下是tchar.h头文件中部分代码摘抄:
1.
#ifdef _UNICODE
2.
typedef
wchar_t
TCHAR
;
3.
#define __T(x) L##x
4.
#define _T(x) __T(x)
5.
#else
6.
#define __T(x) x
7.
typedef
char
TCHAR
;
8.
#endif
可见,这些宏根据”_UNICODE” 定义与否,分别展开为ANSI或Unicode字符。 tchar.h头文件中定义的宏可以分为两类:
A、实现字符和常量字符串定义的宏我们只列出两个最常用的宏:
宏
未定义_UNICODE(ANSI字符)
定义了_UNICODE(Unicode字符)
TCHAR
char
wchar_t
_T(x)
x
L##x
注意:
“##”是ANSI C标准的预处理语法,它叫做“粘贴符号”,表示将前面的L添加到宏参数上。也就是说,如果我们写_T(“Hello”),展开后即为L“Hello”
B、实现字符串函数调用的宏
C++为字符串函数也定义了一系列宏,同样,我们只例举几个常用的宏:
宏
未定义_UNICODE(ANSI字符)
定义了_UNICODE(Unicode字符)
_tcschr
strchr
wcschr
_tcscmp
strcmp
wcscmp
_tcslen
strlen
wcslen
四、使用Win32 API进行Unicode编程
Win32 API中定义了一些自己的字符数据类型。这些数据类型的定义在winnt.h头文件中。例如:
1.
typedef
char
CHAR
;
2.
typedef
unsigned
short
WCHAR
;
// wc, 16-bit UNICODE character
3.
typedef
CONST
CHAR
*
LPCSTR
, *
PCSTR
;
Win32 API在winnt.h头文件中定义了一些实现字符和常量字符串的宏进行ANSI/Unicode通用编程。同样,只例举几个最常用的:
01.
#ifdef UNICODE
02.
typedef
WCHAR
TCHAR
, *
PTCHAR
;
03.
typedef
LPWSTR
LPTCH, PTCH;
04.
typedef
LPWSTR
PTSTR
,
LPTSTR
;
05.
typedef
LPCWSTR
LPCTSTR
;
06.
#define __TEXT(quote) L##quote // r_winnt
07.
#else /* UNICODE */ // r_winnt
08.
typedef
char
TCHAR
, *
PTCHAR
;
09.
typedef
LPSTR
LPTCH, PTCH;
10.
typedef
LPSTR
PTSTR
,
LPTSTR
;
11.
typedef
LPCSTR
LPCTSTR
;
12.
#define __TEXT(quote) quote // r_winnt
13.
#endif /* UNICODE */ // r_winnt
从以上头文件可以看出,winnt.h根据是否定义了UNICODE(没有下划线),进行条件编译。
Win32 API也定义了一套字符串函数,它们根据是否定义了“UNICODE”分别展开为ANSI和Unicode字符串函数。如:lstrlen。API的字符串操作函数和C++的操作函数可以实现相同的功能,所以,如果需要的话,建议您尽可能使用C++的字符串函数,没必要去花太多精力再去学习API的这些东西。
也许您从来没有注意到,Win32 API实际上有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。例如:其实根本没有SetWindowText()这个API函数,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。这些API函数的头文件在winuser.h中声明,下面例举winuser.h中的SetWindowText()函数的声明部分:
1.
#ifdef UNICODE
2.
#define SetWindowText SetWindowTextW
3.
#else
4.
#define SetWindowText SetWindowTextA
5.
#endif // !UNICODE
可见,API函数根据定义UNICODE与否决定指向Unicode版本还是MBCS版本。
细心的读者可能已经注意到了UNICODE和_UNICODE的区别,前者没有下划线,专门用于Windows头文件;后者有一个前缀下划线,专门用于C运行时头文件。换句话说,也就是在ANSI C++语言里面根据_UNICODE(有下划线)定义与否,各宏分别展开为Unicode或ANSI字符,在Windows里面根据UNICODE(无下划线)定义与否,各宏分别展开为Unicode或ANSI字符。
在后面我们将会看到,实际使用中我们不加严格区分,同时定义_UNICODE和UNICODE,以实现UNICODE版本编程。
五、VC++6.0中编写Unicode编码的应用程序
VC++ 6.0支持Unicode编程,但默认的是ANSI,所以开发人员只需要稍微改变一下编写代码的习惯便可以轻松编写支持UNICODE的应用程序。
使用VC++ 6.0进行Unicode编程主要做以下几项工作:
1、为工程添加UNICODE和_UNICODE预处理选项。
具体步骤:打开[工程]->[设置…]对话框,如图1所示,在C/C++标签对话框的“预处理程序定义”中去除_MBCS,加上_UNICODE,UNICODE。(注意中间用逗号隔开)改动后如图2:
图一
图二
在没有定义UNICODE和_UNICODE时,所有函数和类型都默认使用ANSI的版本;在定义了UNICODE和_UNICODE之后,所有的MFC类和Windows API都变成了宽字节版本了。
2、设置程序入口点
因为MFC应用程序有针对Unicode专用的程序入口点,我们要设置entry point。否则就会出现连接错误。
设置entry point的方法是:打开[工程]->[设置…]对话框,在Link页的Output类别的Entry Point里填上wWinMainCRTStartup。
图三
3、使用ANSI/Unicode通用数据类型
微软提供了一些ANSI和Unicode兼容的通用数据类型,我们最常用的数据类型有_T ,TCHAR,LPTSTR,LPCTSTR。
顺便说一下,LPCTSTR和const TCHAR*是完全等同的。其中L表示long指针,这是为了兼容Windows 3.1等16位操作系统遗留下来的,在Win32 中以及其它的32位操作系统中,long指针和near指针及far修饰符都是为了兼容的作用,没有实际意义。P(pointer)表示这是一个指针;C(const)表示是一个常量;T(_T宏)表示兼容ANSI和Unicode,STR(string)表示这个变量是一个字符串。综上可以看出,LPCTSTR表示一个指向常固定地址的可以根据一些宏定义改变语义的字符串。比如:
1.
TCHAR
* szText=_T(“Hello!”);
2.
TCHAR
szText[]=_T(“I Love You”);
3.
LPCTSTR
lpszText=_T(“大家好!”);
使用函数中的参数最好也要有变化,比如:
1.
MessageBox(_T(“你好”));
其实,在上面的语句中,即使您不加_T宏,MessageBox函数也会自动把“你好”字符串进行强制转换。但我还是推荐您使用_T宏,以表示您有Unicode编码意识。
4、修改字符串运算问题
一些字符串操作函数需要获取字符串的字符数(sizeof(szBuffer)/sizeof(TCHAR)),而另一些函数可能需要获取字符串的字节数sizeof(szBuffer)。您应该注意该问题并仔细分析字符串操作函数,以确定能够得到正确的结果。
ANSI操作函数以str开头,如strcpy(),strcat(),strlen();
Unicode操作函数以wcs开头,如wcscpy,wcscpy(),wcslen();
ANSI/Unicode操作函数以_tcs开头 _tcscpy(C运行期库);
ANSI/Unicode操作函数以lstr开头 lstrcpy(Windows函数);
考虑ANSI和Unicode的兼容,我们需要使用以_tcs开头或lstr开头的通用字符串操作函数。
六、举个Unicode编程的例子
第一步:
打开VC++6.0,新建基于对话框的工程Unicode,主对话框IDD_UNICODE_DIALOG中加入一个按钮控件,双击该控件并添加该控件的响应函数:
1.
void
CUnicodeDlg::OnButton1()
2.
{
3.
TCHAR
* str1=_T(
"ANSI和UNICODE编码试验"
);
4.
m_disp=str1;
5.
UpdateData(FALSE);
6.
}
添加静态文本框IDC_DISP,使用ClassWizard给该控件添加CString类型变量m_disp。使用默认ANSI编码环境编译该工程,生成Unicode.exe。
第二步:
打开“控制面板”,单击“日期、时间、语言和区域设置”选项,在“日期、时间、语言和区域设置”窗口中继续单击“区域和语言选项”选项,弹出“区域和语言选项”对话框。在该对话框中,单击“高级”标签,将“非Unicode的程序的语言”选项改为“日语”,单击“应用”按钮,如图四:
图四
弹出的对话框单击“是”,重新启动计算机使设置生效。
运行Unicode.exe程序并单击“Button1”按钮,看,静态文本框出现了乱码。
第三步:
改为Unicode编码环境编译该工程,生成Unicode.exe。再次运行Unicode.exe程序并单击“Button1”按钮。看到Unicode编码的优势了吧。
就说这些吧,祝您好运。
=================================================
使用ANSI/Unicode通用数据类型
微软提供了一些ANSI和Unicode兼容的通用数据类型,我们最常用的数据类型有_T ,TCHAR,LPTSTR,LPCTSTR。
顺便说一下,LPCTSTR和const TCHAR*是完全等同的。其中L表示long指针,这是为了兼容Windows 3.1等16位操作系统遗留下来的,在Win32 中以及其它的32位操作系统中,long指针和near指针及far修饰符都是为了兼容的作用,没有实际意义。P(pointer)表示这是一个指针;C (const)表示是一个常量;T(_T宏)表示兼容ANSI和Unicode,STR(string)表示这个变量是一个字符串。综上可以看出, LPCTSTR表示一个指向常固定地址的可以根据一些宏定义改变语义的字符串。比如:
TCHAR* szText=_T(“Hello!”);TCHAR szText[]=_T(“I Love You”);LPCTSTR lpszText=_T(“大家好!”);
使用函数中的参数最好也要有变化,比如:
MessageBox(_T(“你好”));
其实,在上面的语句中,即使您不加_T宏,MessageBox函数也会自动把“你好”字符串进行强制转换。但我还是推荐您使用_T宏,以表示您有Unicode编码意识。
=================================================
修改字符串运算问题
一些字符串操作函数需要获取字符串的字符数(sizeof(szBuffer)/sizeof(TCHAR)),而另一些函数可能需要获取字符串的字节数sizeof(szBuffer)。您应该注意该问题并仔细分析字符串操作函数,以确定能够得到正确的结果。
ANSI操作函数以str开头,如strcpy(),strcat(),strlen();
Unicode操作函数以wcs开头,如wcscpy,wcscpy(),wcslen();
ANSI/Unicode操作函数以_tcs开头 _tcscpy(C运行期库);
ANSI/Unicode操作函数以lstr开头 lstrcpy(Windows函数);
考虑ANSI和Unicode的兼容,我们需要使用以_tcs开头或lstr开头的通用字符串操作函数。
================================================
在VC6中,默认使用MBCS编码,即多字节字符;而VC8、VC7默认的是Unicode编码,实际就是支持大于0x80的ASCII码。这样,一个中文字可以表示为2个字节,GB2312就是这样表示的。
VC6的默认安装是不带UNICODE库的,要在VC6中写UNICODE程序,必须安装CRT和MFC的Unicode库。要使你的程序支持Unicode,要在你的项目属性中去掉"_MBCS"宏定义,增加"UNICODE"和"_UNICODE"两个宏定义。(注意,这两个都应该加上,因为CRT和MFC使用UNICODE定义,而STL则使用_UNICODE)。
如果你的程序是MFC的,则Unicode版MFC库的入口点是wWinMainCRTStartup。
VS2005工程的默认字符集是Unicode。
为了方便开发者,VC6中提供了Tchar.h,里面定义了一些宏用来帮助写两种编码都兼容的代码。
CRT中的相关函数在Tchar.h中都定义了相应的替代,基本是将str换成了_tcs,
比如:CRT中的unsigned int strlen(const char *)现在是unsigned int _tcslen(const TCHAR*),
在Uniocde时,将被替换为unsigned int _wcslen(const wchar_t)*,
而在MBCS时,会被替换为unsigned int _mcslen(const char*)。
看,写Unicode和MBCS兼容的代码挺容易的吧,我总结了一些替换规则
1 将char换成TCHAR (unsigned char必须去掉unsigned)
2 将str函数换成_tcs函数
3 将字符串常量定义加要_T("")宏
4 printf函数族必须修改为wprintf,不过要注意千万不要使用wprintf函数来解析char型
很多时候程序中既需要Unicode,又需要使用ASCII,这时需要用到操作系统的2个API
WideCharToMultiByte用来将Unicode字符串转化为MBCS的
MultiByteToWideChar用来将MBCS字符串转化为Unicode的
一些注意事项:
在Unicode编码下,sizeof没那么可靠了,memset( 0 sizeof())的习惯用法可能会出大错,改成memset(0sizeof()/szieof(TCHAR))就没事了,呵呵
在Unicode下,一个中文字符就是一个字符,len = strlen() / 2;这样可不行了
用VC6进行UNICODE编程
最近试图将自己的程序编译成Unicode版本,费了不少力气,相关内容整理如下,适用于VC6,但VC7、VC8应该也差不多的(后者新建项目缺省即按Unicode编译)。
1. 添加 UNICODE 和 _UNICODE 预处理定义
位置:Project Settings -> C/C++ -> Preprocessor definitions
添加了这两个定义后,MFC的一些内置类型如 TCHAR、CString 都将转为支持宽字符类型(wchar_t)
2. 使用宽字符相关类型,如:
char -> TCHAR、char * -> LPTSTR、const char * -> LPCTSTR
3. 对字符串常量使用 _T() 宏
4. 替换C库中的中字符串操作函数,如 strlen -> _tcslen、strcmp -> _tcscmp 等
类似的还有C库中字符串与数字的转换函数,如 atoi -> _ttoi、itoa -> _itot 等
5. 将 Project Settings -> link -> Output -> Entry Point 设为 wWinMainCRTSTartup
否则会有如下错误:
msvcrtd.lib(crtexew.obj) : error LNK2001: unresolved external symbol _WinMain@16
6. C++标准库中的string,有对应的宽字符版本wstring,两者均为basic_string的特化版本
可在StdAfx.h中:
#ifdef _UNICODE
#define tstring wstring
#else
#define tstring string
#endif
然后在代码中使用 tstring 即可,类似的还有 fstream/wfstream、ofstream/wofstream 等
7. 宽字符版本的英文字符仍可直接与整型值进行比较,如:
CString s = _T("ABC");
ASSERT(s[0] == 'A');
8. 对于仍需使用ANSI字符串的地方,如第三方类库的接口,仍可继续使用;如需进行Unicode字符串和ANSI字符串的互转换,可使用 MultiByteToWideChar 和 WideCharToMultiByte
=================================================
在Windows下用VC编程,如果编写的程序要在多种语言环境下运行(比如日文、中文、葡萄牙文等),使用VC默认的MBCS编译选项就会出现乱码,甚至导致程序崩溃。要克服这一缺点,就需要使用Unicode编程,简要说明一下Unicode:
Unicode也是一种字符编码方法,它占用两个字节(0000H―FFFFH),容纳65536个字符,这完全可以容纳全世界所有语言文字的编码。在Unicode里,所有的文字都按一个字符来处理,它们都有一个唯一的Unicode码。
Windows NT及后续系统的内核都是基于Unicode的。在Windows内核中,宏UNICODE指示是否启用Unicode,而C++是根据_UNICODE宏来判断的,因此在编程中我们要把这两个宏写进预处理参数里。
比如在tchar.h头文件中,有如下声明:
#define _T(x) __T(x) #ifdef _UNICODE typedef wchar_t TCHAR; #define __T(x) L##x #else typedef char TCHAR; #define __T(x) x #endif
而在winnt.h头文件中,定义了如下数据类型:
typedef char CHAR, *LPSTR; typedef CONST CHAR *LPCSTR, *PCSTR; typedef unsigned short WCHAR,*LPWSTR; // 16-bit UNICODE character typedef CONST WCHAR *LPCWSTR, *PCWSTR; // #ifdef UNICODE typedef WCHAR TCHAR, *PTCHAR; typedef LPWSTR LPTCH, PTCH; typedef LPWSTR PTSTR, LPTSTR; typedef LPCWSTR LPCTSTR; #define __TEXT(quote) L##quote #else typedef char TCHAR, *PTCHAR; typedef LPSTR LPTCH, PTCH; typedef LPSTR PTSTR, LPTSTR; typedef LPCSTR LPCTSTR; #define __TEXT(quote) quote #endif /* UNICODE */
实际上Win32 API有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。例如:其实根本没有SetWindowText()这个API函数,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。这些API函数的头文件在winuser.h中声明,下面例举winuser.h中的SetWindowText()函数的声明部分:
#ifdef UNICODE #define SetWindowText SetWindowTextW #else #define SetWindowText SetWindowTextA #endif // !UNICODE
在VC6.0下使用Unicode的步骤如下:
1、project->Settings…->C/C++->Preprocessor Definitions,删除_MBCS,然后添加_UNICODE,UNICODE。
2、project->Settings…->Link->Category选择Output,Entry-point Symbol栏填入wWinMainCRTStartup。
【注】如果是不是exe工程(比如DLL或LIB),不执行第二个步骤,否则会出现warning LNK4086错误。
C++使用wchar_t来表示一个宽字符,它在内部被定义为unsigned short,占两个字节。相对于普通字符,C++有一整套的宽字符操纵函数, 以下是一份宽字符处理函数函数与普通函数对照表:
宽字符处理函数函数与普通函数对照表 字符分类: 宽字符函数 普通C函数 描述 iswalnum() isalnum() 测试字符是否为数字或字母 iswalpha() isalpha() 测试字符是否是字母 iswcntrl() iscntrl() 测试字符是否是控制符 iswdigit() isdigit() 测试字符是否为数字 iswgraph() isgraph() 测试字符是否是可见字符 iswlower() islower() 测试字符是否是小写字符 iswprint() isprint() 测试字符是否是可打印字符 iswpunct() ispunct() 测试字符是否是标点符号 iswspace() isspace() 测试字符是否是空白符号 iswupper() isupper() 测试字符是否是大写字符 iswxdigit() isxdigit() 测试字符是否是十六进制的数字 大小写转换: 宽字符函数 普通C函数 描述 towlower() tolower() 把字符转换为小写 towupper() toupper() 把字符转换为大写 字符比较: 宽字符函数 普通C函数 描述 wcscoll() strcoll() 比较字符串 日期和时间转换: 宽字符函数 描述 strftime() 根据指定的字符串格式和locale设置格式化日期和时间 wcsftime() 根据指定的字符串格式和locale设置格式化日期和时间, 并返回宽字符串 strptime() 根据指定格式把字符串转换为时间值, 是strftime的反过程 打印和扫描字符串: 宽字符函数 描述 fprintf()/fwprintf() 使用vararg参量的格式化输出 fscanf()/fwscanf() 格式化读入 printf() 使用vararg参量的格式化输出到标准输出 scanf() 从标准输入的格式化读入 sprintf()/swprintf() 根据vararg参量表格式化成字符串 sscanf() 以字符串作格式化读入 vfprintf()/vfwprintf() 使用stdarg参量表格式化输出到文件 vprintf() 使用stdarg参量表格式化输出到标准输出 vsprintf()/vswprintf() 格式化stdarg参量表并写到字符串 数字转换: 宽字符函数 普通C函数 描述 wcstod() strtod() 把宽字符的初始部分转换为双精度浮点数 wcstol() strtol() 把宽字符的初始部分转换为长整数 wcstoul() strtoul() 把宽字符的初始部分转换为无符号长整数 多字节字符和宽字符转换及操作: 宽字符函数 描述 mblen() 根据locale的设置确定字符的字节数 mbstowcs() 把多字节字符串转换为宽字符串 mbtowc()/btowc() 把多字节字符转换为宽字符 wcstombs() 把宽字符串转换为多字节字符串 wctomb()/wctob() 把宽字符转换为多字节字符 输入和输出: 宽字符函数 普通C函数 描述 fgetwc() fgetc() 从流中读入一个字符并转换为宽字符 fgetws() fgets() 从流中读入一个字符串并转换为宽字符串 fputwc() fputc() 把宽字符转换为多字节字符并且输出到标准输出 fputws() fputs() 把宽字符串转换为多字节字符并且输出到标准输出串 getwc() getc() 从标准输入中读取字符, 并且转换为宽字符 getwchar() getchar() 从标准输入中读取字符, 并且转换为宽字符 None gets() 使用fgetws() putwc() putc() 把宽字符转换成多字节字符并且写到标准输出 putwchar() getchar() 把宽字符转换成多字节字符并且写到标准输出 None puts() 使用fputws() ungetwc() ungetc() 把一个宽字符放回到输入流中 字符串操作: 宽字符函数 普通C函数 描述 wcscat() strcat() 把一个字符串接到另一个字符串的尾部 wcsncat() strncat() 类似于wcscat(), 而且指定粘接字符串的粘接长度. wcschr() strchr() 查找子字符串的第一个位置 wcsrchr() strrchr() 从尾部开始查找子字符串出现的第一个位置 wcspbrk() strpbrk() 从一字符字符串中查找另一字符串中任何一个字符第一次出现的位置 wcswcs()/wcsstr() strchr()在一字符串中查找另一字符串第一次出现的位置 wcscspn() strcspn() 返回不包含第二个字符串的的初始数目 wcsspn() strspn() 返回包含第二个字符串的初始数目 wcscpy() strcpy() 拷贝字符串 wcsncpy() strncpy() 类似于wcscpy(), 同时指定拷贝的数目 wcscmp() strcmp() 比较两个宽字符串 wcsncmp() strncmp() 类似于wcscmp(), 还要指定比较字符字符串的数目 wcslen() strlen() 获得宽字符串的数目 wcstok() strtok() 根据标示符把宽字符串分解成一系列字符串 wcswidth() None 获得宽字符串的宽度 wcwidth() None 获得宽字符的宽度 另外还有对应于memory操作的 wmemcpy(),wmemchr(),wmemcmp(),wmemmove(),wmemset()。
Unicode编程中,如果需要声明一个宽字符串,需要这样写:
wchar_t *wstr = L”Hello”;
其中字符”L”告诉编译器你要构造的是一个宽字符串,”L”和字符串之间不能有空格。
虽然上述声明字符串的代码是正确的,但是并不提倡这样做,因为程序可移植性太差。
还记得前面介绍的几个宏么?_T(x)会在_UNICODE定义了的情况下被扩展为L##x, 而在一般情况下被扩展为x;TCHAR则分别被替换为wchar_t和char。因此我们可以这样写:
TCHAR *str = _T(“Hello”);
这样,如果_UNICODE宏被定义了,则它被扩展为:
wchar_t *wstr = L”Hello”;
否则,在默认情况下被扩展为:
char *str = “Hello”;
如果需要写一个库,而且要分别提供Unicode和非Unicode版本,那么仅仅许多修改两个UNICODE宏就可以了,不需要修改任何代码。
如果非常不幸,你的项目在一开始没有被设计为使用Unicode(没有使用_T()宏和TCHAR等类型),而现在出于国际化的需要要使其支持Unicode,那么在添加两个UNICODE宏和函数入口点后会可能会出现无数个编译错误(我遇到过566个的)。虽然修改的方式根据项目而不同,但也多少有点相似之处,有步骤地做总比漫无目的得改好。
1、搜索所有的AfxMessagebox和Messagebox函数,将其中的字符串加上_T()宏。
2、搜索所有的str.Format函数,为第一个参数加上_T()宏。
3、为字符串常量加上_T()宏。
4、将strlen、strcpy等函数替换为wcslen、wcscpy等宽字符版本。
5、如果wcsncpy、wcsncmp等函数的第三个参数是sizeof(dst),那么现在就要改为sizeof(dst)/2,或者自定义一个宏tsizeof来实现。
6、如果某个函数确实需要char*等类型的参数,使用T2A()宏对参数进行转换,并在所在函数开头添加”USES_CONVERSION;”。
7、查找所有的char* p = (LPSTR)(LPCTSTR)CString这样的强制转换代码,并用char *p = T2A(CString);代替。
通常修改完以上内容,再次编译时错误应该减少了大半了,现在再一个一个地对照修改就容易多了。
最后,配置文件也要存储为Unicode的形式。Unicode的文件头有个0xFEFF标识,如果你是通过::WritePrivateProfileString()来写入配置文件的,那么只需要在调用此API之前往文件里写入0xFEFF文件头,此后WritePrivateProfileString会自动将后续内容保存成Unicode的形式。为了简单,可以讲程序中调用的::WritePrivateProfileString()全都替换成如下改写版本即可:
static BOOL _WritePrivateProfileString(LPCTSTR lpAppName, // section name LPCTSTR lpKeyName, // key name LPCTSTR lpString, // string to add LPCTSTR lpFileName // initialization file ) { FILE *fp; fp = _tfopen(lpFileName, _T("r")); if (fp == NULL) { fp=_tfopen(lpFileName, _T("w+b")); wchar_t m_strUnicode[1]; m_strUnicode[0] = wchar_t(0XFEFF); fputwc(*m_strUnicode,fp); } fclose(fp); return ::WritePrivateProfileString(lpAppName, lpKeyName, lpString, lpFileName); }
==================================================
Unicode编码系统可分为编码方式和实现方式两个层次。
1.编码方式
通用字符集(Universal Character Set,UCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的标准字符集。UCS-2用两个字节编码,UCS-4用4个字节编码。
UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个平面(plane)。每个平面根据第3个字节分为256行 (row),每行有256个码位(cell)。group 0的平面0被称作BMP(Basic Multilingual Plane)。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。每个平面有2^16=65536个码位。
Unicode计划使用了17个平面,一共17*65536=1114112个码位。
2.实现方式
UTF是“UCS Transformation Format”的缩写,可以翻译成Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。UTF-8、UTF-16、UTF-32分别以BYTE、WORD、DWORD作为编码单位。
2.1 UTF-8
UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。
2.2 UTF-16
UTF-16编码以2个字节为单位。我们把Unicode编码记作U。编码规则如下:
如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数
如果U≥0x10000,我们先计算U'=U-0x10000,然后将U'写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。故Unicode编码0x10000-0x10FFFF的UTF-16编码有两个WORD,也就是4字节。
为什么U'可以被写成20个二进制位?Unicode的最大码位是0x10ffff,减去0x10000后,U'的最大值是0xfffff,所以肯定可以用20个二进制位表示。
2.3 UTF-32
UTF-32编码以4个字节为单位。Unicode的UTF-32编码就是其对应的32位无符号整数。
总结起来,可以这样理解UTF-8/16/32,UTF-16是最初的Unicode编码形式,其原理类似DBCS,但是显然2个字节并不能够将全世界语言完全编码;于是乎,UTF-32出现了,4字节的编码肯定是足够了,但是用着用着发现,有很多字符的编码其高位都是0,太浪费空间;于是乎UTF-8和UTF-16变异体出现了,其原理类似于MBCS,即使用变长字节对字符进行编码。