绕死你不偿命的UNICODE、_UNICODE、__TEXT、__T、_T、_TEXT、TEXT宏
这是我在博客园的博客中的文章。
下面是原文(未大改,但删去了最后一段废话,另外,加上了文章后面回复中有水平的两个):
最近在看一些关于VC++和MFC的书时,书上对字符串的处理一般都会使用 TEXT("a string")
的形式或者 _T("a string")
的形式,自己写程序时MFC自动生成的代码中也有类似的宏。作为菜鸟,不加思考地照搬书上的 TEXT()
或者 _T()
不是我的风格,喜欢追根究底的性格促使我决定弄懂这些宏。但如果我按照以往写文章的习惯,跟着我思考的顺序来写这篇随笔的话,那是倒叙,会很不好写,所以我就按弄懂之后的正常顺序来写吧,但这也让这篇随笔看起来有点说教,各位看客且请忍受一下,谢谢。
C语言发明时尚没有UNICODE这一说,那时候米国人只有ASCII,但随着计算机的进化,程序中需要出现其它国家语言的字符,如中文,这远不是ASCII所能表示的,所以就出现了UNICODE(想深入了解UNICODE的童鞋,请猛击这里),于是C语言也新加了一个类型: wchar_t
,用于表示UNICODE字符,其定义为: #define unsigned short wchar_t
,说白了其实就是用16位双字节表示一个字符(ASCII用单字节表示一个字符)。
这样编写程序就出现了一个问题,我先定义了一个变量: char *str = "china";
这当然没问题,但如果后来要把该字符串换成中文的,那就得换成 wchar_t *str = L"中国";
(顺便说一下,字符串前的L是告诉编译器,后面紧跟的字符串按UNICODE宽字符处理,即每个字符占两个字节)。如果字符串只有一个,改一下没问题,但很难想象一个程序中只出现一个字符串,所以,这样修改起来的工作量是很大的。
M$永远不会缺乏牛人,所以他们自然为VC++想好了解决办法,那就是 __TEXT(), __T()
等一系列宏,先来看看WinNT.h头文件,这个文件有几千行,但我们只需要抽取关键的几行出来:
#ifdef UNICODE // r_winnt #define __TEXT(quote) L##quote // r_winnt #else /* UNICODE */ // r_winnt #define __TEXT(quote) quote // r_winnt #endif /* UNICODE */ // r_winnt #define TEXT(quote) __TEXT(quote) // r_winnt
这几行代码应该很简单:如果定义了UNICODE宏,那么就将 __TEXT(quote)
宏定义为 L##quote
,如果没定义UNICODE宏,则 __TEXT(quote)
宏就是普通的 quote
,最后,再将 TEXT(quote)
宏定义为 __TEXT(quote)
宏。(如果有朋友不懂其中##符号的意思的话,这里解释一下,##起连接作用,例如 __TEXT("china")
在经过宏替换后就会被解释为L"china")
这种解决方法很巧妙,编程人员可以根据需要自由定义UNICODE宏来决定是使用ASCII字符串还是UNCODE字符串,而程序中的字符、字符串只需加个宏处理即可。与此类似的还有 __T(), _TEXT()
等宏,这些宏在tchar.h头文件里定义,这个文件同样有几千行,仍然只需要抽出关键的几行:
#ifdef _UNICODE #define __T(x) L ## x #else /* ndef _UNICODE */ #define __T(x) x #endif /* _UNICODE */ #define _T(x) __T(x) #define _TEXT(x) __T(x)
其原理同上,但这里是根据_UNICODE宏(注意前面的下划线)来决定是使用ASCII字符还是UNICODE字符的。同样最后又附加了 _T()
和 _TEXT()
宏的定义。
到此,疑问又来了,为什么要定义_UNICODE和UNICODE两个宏?M$的牛人吃多了嫌撑?要是一个宏定义了,另一个宏没定义引起冲突怎么办?于是再来查找一番,在atldef.h和afxv_w32.h两个头文件中,我找到了一模一样的内容,如下:
#ifdef _UNICODE #ifndef UNICODE #define UNICODE // UNICODE is used by Windows headers #endif #endif #ifdef UNICODE #ifndef _UNICODE #define _UNICODE // _UNICODE is used by C-runtime/MFC headers #endif #endif
这段代码有点绕,但目的很清晰,就是要保证UNICODE和_UNICODE这两个宏要么都定义了,要么都未定义,不能只定义一个。所以,在上面的分析中,不论是利用UNICODE宏来定义的 __TEXT()
和 TEXT()
,还是利用_UNICODE宏来定义的 __T()
、 _T()
和 _TEXT()
,都是可以正常使用的,不会出现一部分是ASCII字符串,另一部分是UNICODE字符串的低等错误。因此, __TEXT, __T, _T, _TEXT, TEXT
这几个宏的具体作用其实是一样的,没有区别,至于M$为什么对相同的功能要搞这么多奇形怪状的符号来表示,那我就不得而知了。
同时,上面的注释还解释了,UNICODE宏用于Windows头文件,而_UNICODE宏用于C运行时和MFC的头文件,当然我这个菜鸟还不太懂具体区别,只能大概猜到,在Windows的头文件中,需要根据是否使用UNICODE来定义不同版本宏的地方就使用UNICODE宏,而在MFC和标准C中,需要根据是否使用UNICODE来定义不同版本宏的地方就使用_UNICODE宏。
下面是文章后面的评论中,网友Zhenxing Zhou给出的评论:
在标准出现之前,编译器自己扩展的,一般以下划线开始,如_UNICODE,当UNICODE成为标准后,由于兼容问题,_UNICODE也应该同时定义,就出现了两个*UNICODE。
#define unsigned short wchar_t为了兼容早期不支持wchar_t的编译器。
下面是网友egmkang给出的评论:
- char* str = "xxx"; 这里的str编码是未知的,但是ASCII兼容的.比如说gd2312,utf-8他就是ASCII兼容的.是可以用来表示中文的.实际的编码跟文件的encoding有关系.
- L表示后续的字符串是Unicode编码的.但是这个Unicode编码说的很宽泛,UTF-16是Unicode,UTF-32也是Unicode.很不幸,这两种编码在C语言的实现里面同时存在.比如Windows里面用的就是UTF-16,Unix-Like呢,就是UTF-32...
- 马上将要到来的C1X会加强对unicode编码的支持.呵呵,但是VC应该不会支持C1X吧,因为C99到现在VC都不支持..