API入门系列之三 -那迷惑人的Windows字符和字符指针类型
原创文章,转载请注明作者及出处。
首发
http://blog.csdn.net/beyondcode
http://www.cnblogs.com/beyond-code/
http://hi.baidu.com/beyondcode
大家好,通过前面两篇打头文章,我也看了留言,感谢那些给我提意见的人和指出错误之处的人。再次谢谢你们的支持。 另外,Windows SDK编程交流群已经建立了,欢迎各位志同道合者加入进行交流( 群号:81543028 )
下面我罗列一些我们在Windows平台下编程经常使用到的和字符或字符串有关的数据类型。
char 和 wchar_t
这两个类型大家绝对不会陌生吧,一个是单字节的字符类型,一个是宽字节的字符类型(也就是Unicode字符)。
char c = 'b';
wcha_t wc = L'b';
上面我就分别定义了2个变量c和wc ,相信第一个定义大家都看的懂,就是定一个字符变量c,其中保存了'b'这个字符。 那么第二个呢? 我相信还是很多人都看的懂,要是你看不懂也没关系,现在就告诉你,也是定义一个字符变量wc, 只不过这个字符变量是Unicode字符变量,用2个字节来保存一个字符,而上面的c这个字符变量只有一个字节来保存,那么在'b'前面的L又是什么意思呢,它就表示这里的'b'这个字符是一个Unicode字符,所以第二个定义的意思就是将L'b'这个Unicode字符保存到wc这个Unicode字符变量中。
如果我要定义一个字符数组怎么定义呢? 用分别用单字节的char和宽字节的wchar_t来定义就应该是:
char c[10];
wchar_t wc[10];
如果是要带初始化的字符数组的声明,我们来看看怎么写
char c[] = "beyondcode";
wchar_t wc[] = L"beyondcode";
看到了吗,宽字节的操作其实和单字节的字符操作一样吧,只是在前面加上L表示是宽字节的字符或者字符串。
上面都是属于C/C++中的知识,并没有涉及太多Windows中的数据类型,那么各位朋友们在Windows编程中看到的满到处都是的 TCHAR,LPSTR, LPCSTR, LPWSTR, LPCWSTR, LPTSTR, LPCTSTR 这些数据类型又是怎么回事呢? 别急,我们一步一步的来,最后我会联系到那上面去的。
上面的你都知道或者是理解了的话,那我们继续,除了可以声明一个字符数组,我还可以定义一个字符指针变量来指向一个字符数组,当然这个字符数组可以是Unicode的宽字节字符数组,也可以是单字节字符数组,如下:
char c[] = "hello beyondcode"; //定义一个字符数组
wchar_t wc[] = L"hello beyondcode"; //定义一个宽字节字符数组
char *p = c; //定义一个字符指针,指向刚才的字符数组
wchar_t *wp = wc; //定义一个宽字节字符指针,指向刚才的宽字节字符数组
这样之后,我就可以通过指针来改变刚才我们定义的2个数组,例如:
p[0] = 'H';
wp[0] = L'H';
把上面2个数组的第一个字符通过指针改变成大写。这里是可以通过指针来修改的,因为我没有定义指针为常量指针,也就是没有加const 修饰符。如果我像下面这样定义的话,那么就不能通过这些指针来改变他们所指向的数据了,而是只有读取他们。
const char *p = c;
const wchar_t *wp = wc;
上面将的都是C/C++的基础知识,有点啰嗦,为了照顾新手朋友们嘛,下面我们就来看看Windows是怎么定义它的数据类型的
首先,定义了CHAR, WCHAR的这2个字符数据类型,就是我们上面讨论的两个字符数据类型改了一下名字而已。现在你还不昏吧··
typedef char CHAR;
typedef wchar_t WCHAR;
然后,用刚才定义的 CHAR, WCHAR这2个字符数据类型去定义了一系列其他字符指针类型。
typedef CHAR *LPSTR;
typedef WCHAR *LPWSTR;
这样一定义之后,LPSTR的就是 CHAR*, 而CHAR 又是char, 所以LPSTR的本质就是 char*,也就是我们上面熟悉的不能再熟悉的字符指针, 那LPWSTR不用我推导,相信你也推导出来了吧。不过我还是推导一下,LPWSTR是 WCHAR * , WCHAR是wchar_t,这样LPWSTR就是 wchar_t* ,也就是我们上面讨论的宽字节字符指针。上面这些定义都是在WinNT.h这个头文件中定义的,读者朋友们有兴趣在这个头文件里面去挖掘挖掘吧,上面2个定义我只是提取了重要的部分,其实在里面他还定义了其他很多别名.
看了LPSTR, LPWSTR是怎么一回事之后,我们再接再厉,看看LPCSTR,LPCWSTR这2个数据类型又是怎么一回事呢, 老规矩,先看windows的定义。
typedef CONST CHAR *LPCSTR;
typedef CONST WCHAR *LPCWSTR;
和上面的比较,名字中就多了一个大写的C,这个C的含义就代表是const修饰符,也就是我们上面所说的常量指针,指向的内容不能通过这个指针被改变,但可以读取。定义中的大写的CONST也是一个宏,我在第一篇文章中就讲过了,代换出来也就是const, 所以请读者自己推导一下这两个数据类型的本质是什么。
所以,在windows平台下的编程过程中,凡是可以使用char* 的地方,你都可以使用LPSTR来代替,凡是可以使用wchar_t*的地方,你都可以使用LPWSTR来代替,至于怎么用,还是那句老话,看你个人心情,只不过Windows的API函数中关于字符串的都是使用LP这种数据类型。但是你还是可以给他传递char* 或者 wchar_t* ,只要他们的本质是一样的,那怎么不可以呢~~
下面,我们来看一看一些示例。
char c = 'c'; 和 CHAR c = 'c'; 是一样的
wchar_t wc = L'w'; 和 WCHAR wc = L'w'; 是一样的
char* p 和 LPSTR p 是一样的
wchar_t* wp 和 LPWSTR wp 是一样的
再来看看动态内存分配怎么写的呢
char* p = new char[10]; //动态分配了十个字符
也可以写成
CHAR* p = new CHAR[10];
LPSTR p = new CHAR[10];
LPSTR p = new char[10];
宽字节的再来一次
wchar_t* wp = new wchar_t[10];
也可以写成下面这些形式
WCHAR* wp = new WCHAR[10];
LPWSTR wp = new WCHAR[10];
LPWSTR wp = new wchar_t[10];
上面定义的这些字符指针 p , wp都没有用const修饰符,所以可以通过他们来修改他们所指向的内容。这里留给读者一个问题,怎么定义有const修饰符的字符指针呢,都可以用什么形式来写呢,写得越多越好哟。。
通过上面这些,我想你大概已经了解了LPSTR, LPCSTR, LPWSTR, LPCWSTR这四个数据类型了,他们无非就是:
LPSTR ------- char*
LPCSTR ------- const char*
LPWSTR ------- wchar_t*
LPCWSTR -------- const wchar_t*
下面我提一个问题,如果你在你的程序中使用的字符串都是通过LPWSTR,LPCWSTR这种宽字节(Unicode)字符指针来进行操作的,那么在Unicode环境下编译,完全没有问题,如果这时你需要编译一套ASCII版本的程序,那你会怎么办呢? 你说将用LPWSTR 和LPCWSTR的地方全部换成LPSTR和LPCSTR,再将字符串前面的L去掉就可以了,对,这是一种方法,但是!!所有人在这里都应该知道我要说但是,这也太麻烦了吧。难道没有通用点的方法吗? 有!! 所有人在这里也都知道我会说有,呵呵。 那就是使用微软的通用数据类型,说通用数据类型有点太专业了,其实也就那样,请听我慢慢分析来。我在上一篇文章中说过,凡是涉及字符串操作的API函数有2套,一个A系列的,一套W系列的,还有一套宏,能根据不同的工程环境定义成不同的API函数名。那么在字符类型上微软也使用几乎同样的技术,定义了一套宏能根据不同的工程环境定义成不同的字符数据类型。我上面就提到过的TCHAR,LPTSTR, LPCTSTR就是这样的类型。
首先说说TCHAR,它是被这样定义的:
#ifdef UNICODE
typedef WCHAR TCHAR;
#else
typedef char TCHAR
看到了吗? 它也是根据UNICODE这个宏被定义没有,如果被定义了,那么TCHAR代表的数据类型就是WCHAR, 也就是wchar_t, 如果没被定义,那么TCHAR 就代表的是char
同样LPTSTR,LPCTSTR也是这样的,考虑到篇幅,我就只列出LPTSTR来给大家看看了
#ifdef UNICODE
typedef LPWSTR LPTSTR;
#else
typedef LPSTR LPTSTR;
这个是我简化了的定义,真实面目有些复杂,不过意思也是如此,有兴趣可以自己看看,在WinNT.h这个头文件中。下面再次解释一下上面这个LPTSTR的定义, 还是老样子,根据UNICODE这个宏被定义与否来决定怎么定义LPTSTR ,如果是定义了UNICODE这个宏,表示当前工程环境是Unicode环境,那么LPTSTR就被定义为了LPWSTR, LPWSTR就是我们前面所讲的wchar_t* ,所以此时LPTSTR代表的数据类型就是wchar_t* , 如果这时的工程没有定义UNICODE这个宏,那么就定义LPTSTR为LPSTR,而LPSTR就是我们前面所说的char* ,所以这是的LPTSTR就代表char*。懂了吗?各位,我都觉得自己有些啰嗦了··不好意思···
然后还有一个宏需要讲一下,由于我们使用通用数据类型,那么我事先就不知道我的源代码需要在Unicode下编译还是在ASCII环境下编译,所以如下这种情况
TCHAR tc = 'a'; 或者是 TCHAR tc = L'a'; 是否合适呢? 前面我已经说过了字符或字符串常量前面加L代表这是宽字节的字符或字符串,将一个宽字节字符赋值给一个TCHAR数据类型的变量tc,什么情况下是正确的呢? 各位思考一下呢?
如果当前工程是Unicode环境,那么TCHAR数据类型就是wchar_t的宽字节类型,所以tc就是宽字节字符变量,那么上面第二个赋值语句就是正确的,而第一个就是错误的。
如果反过来,当前的工程是ASCII环境,那么TCHAR代表的是char这种数据类型,那么第一个赋值语句就是正确的,而第二个就是错误的了。
分析了这么多,我就是要讲一个宏 _T(), 只要将字符或者字符串常量放在_T()这个宏里面,那么这个宏就能根据当前的环境决定是否在字符或字符串前面加L,如下面:
TCHAR tc = _T('A');
如果这么写,在不需要改写源代码的情况下,就可以编译出Unicode和ASCII两套程序
而只需要改变工程的环境而已。
这篇文章的内容大概就这么多了,关于后续文章的内容安排,我会适当采纳各位朋友的留言来进行安排。
在这里我介绍的是Windows平台下的和字符串操作有关的数据类型,至于MFC中的CString类,c++标准库中的string,我就不做讲解了。
还有,前两篇的文章中我说微软定义这些数据类型为大写是为了编码的方便,不需要切换输入法,很多朋友都留言给我指出了,其实我是开了一个小玩笑,毕竟面对的是初学者,我觉得在目前这种环境下,没有必要给他们讲什么代码移植性啊,兼容性这些概念,那些在达到一定程度之后就会慢慢理解的,所以我自己想了一个小小的优点来说明那个问题,不过还是感谢你们的补充说明,谢谢··