主题:中文编码问题,请指教
wushaowu
[专家分:0] 发布于 2010-08-26 23:36:00
我的代码如下:
TCHAR c[300];
TCHAR a[]=TEXT("在这里");
TCHAR b='在';
swprintf(c,L"b的长度:%d,b的ascii码:%d,a[0]的长度:%d,a[0]的ascii码:%d,54490为%c,22312为%c",sizeof
(b),b,sizeof(a[0]),a[0],54490,22312);
MessageBox(NULL,c,L"ok",0);
信息显示:'在'的ascii码为54490,而a[0]的ascii的码却为22312,a[0]中的字符也是“在”字呀,这是怎么回事呀?
回复列表 (共3个回复)
沙发
bombless [专家分:50] 发布于 2010-08-27 11:37:00
[img]http://i38.tinypic.com/n19chh.png[/img]
TCHAR b='在';
把这行的‘在’改为L('在')或者TEXT('在')就可以了。
你的C语言文件本身是保存为ANSI编码的(在中文的Windows中具体来说就是codepage 936编码。),因此你需要在字符前加上L或者是字符安全的TEXT才行。
否则就是codepage 936编码的在了。
另外解释一个也许你搞错了的问题。从你的sizeof(TCHAR)为2可以看出你实际上指定了编译器使用Unicode编码。所以a[0]的编码这一段显示的是Unicode编码,而不是所谓的ascii编码。而b恰好就是ANSI本地编码,也就是codepage 936中文编码了。
这个ascii的问题三言两语讲不清楚,我推荐你去看我过去写的几篇博文:
http://hi.baidu.com/xyk34/blog/item/1a167a51080cab2943a75b08.html
关键就是你需要理解ascii编码是不包含汉字的,而且编码汉字的问题是微软90年代为了推出中文版的Windows 95而特意准备了一套解决方案并沿用至今。
所以平时在中文Windows上练习C语言时使用汉字实际上是使用了codepage 936编码。(习惯上有时叫中文Windows中的这种编码为GBK或者EUC-CN编码,不过这2种叫法不够严谨,基本上是错误的)
我们平时在这种codepage 936文字编码中使用英文字符时,之所以可以沿用老师教的ASCII编码的那种规则(例如将小写的a转化成大写的A需要减去32)是因为codepage 936这种中文编码方法,如果一段字符串里只有ASCII字符集里的字符的话,那刚好就是ascii编码。
(实际上codepage 936编码的规则就是,ASCII字符集里已经有的字符就用单个字节的ASCII编码保持不变(要记得,ascii编码的字符总是为单个字节,在C语言里是靠带符号的char类型编码的,范围在0~127之间),而遇到其他字符就用2个最高位是1的字节来编码)。
老师一般都没有讲清楚,在中文版的Windows中,C语言编译器一般都沿用中文版的Windows使用的codepage 936字符集。他们仍然在教学生,计算机都是使用ASCII编码的。(TC 2.0不是这样,TC2.0使用OEM字符集,这个字符集在80年代的PC机上几乎是一个标准)。
不过这种问题一般问老师也没用,根据我的经验,多数教师分不清这个字符集的问题,甚至很多老师还在使用产生DOS程序的TC 2.0(而在93年,VC++ 1.0的时候就不支持DOS程序了(只能支持Windows程序)。我宁愿看到多数人在用不支持标准C语言的VC6也不想看到许多人在用产生DOS程序的TC。
最新的Windows 7操作系统仍然兼容这些DOS程序,我很希望在近2年推出的Windows 8中能完全放弃对DOS程序的兼容,这样至少能加速TC 2.0的死亡。
(作为补充说明,如果你在中文以外的语言的Windows上运行包含中文的Windows程序,而且程序又不是用Unicode编码字符的话,显示就不正常了。不知做这种补充是不是有利于你理解)
另外,我觉得编译器是不能混合ANSI编码和Unicode编码的。所以你使用了swprintf的程序,TCHAR型变量的大小就必然是2,而且必然是Unicode编码。
板凳
bombless [专家分:50] 发布于 2010-08-27 13:44:00
编写了一个小程序看看是不是有助于楼主理解。
关于“在”字在中文Windows中的编码以及其区位码,可以参考我的1篇转载:
http://hi.baidu.com/xyk34/blog/item/36b3f5dfed2c201e6227980a.html
另外,在中文Windows采用的codepage 936文字编码中,凡是有区位码的字符,都是采用区码和位码分别加上0xA0来编码成2个字节的。
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
int main(int argc, char *argv[])
{
TCHAR str[500];
TCHAR a=L'在';
TCHAR b='在';
swprintf(str,
L"\"%c\"字的区位码经过查表可以得到是52,58,\n"
L"而它在codepage 936中的值经过验证,为%d,\n"
L"将它的高字节和低字节分离,得到%d和%d,\n"
L"再将每个字节减去0xA0,就得到其实际的区位码,%d,%d\n"
L"这与我们的猜想%s",
a,b,b>>8,b&0xff,(b>>8)-0xA0,(b&0xff)-0xA0,
(52==(b>>8)-0xA0 && 58==(b&0xff)-0xA0) ? L"相符合":L"不符合");
MessageBox(0,str,L"ok",0x30);
return 0;
}
我在VC6和VC2008里都试过了,成功编译,而且确证了我的判断。
3 楼
bombless [专家分:50] 发布于 2010-08-27 14:08:00
由于上面这个程序还需要额外设置(VC2008的Console Application默认并不使用Unicode,而VC6也是如此),为了方便大家实验,给一个不需要设置的版本:
#define UNICODE
#define _UNICODE
#include <tchar.h>
#include <windows.h>
int _tmain()//宽字符版的main函数。这里同样也可以使用普通的main替代
{
WCHAR str[500];
WCHAR a=L'在';
WCHAR b='在';
swprintf(str,
L"\"%c\"字的区位码经过查表可以得到是52,58,\n"
L"而它在codepage 936中的值经过验证,为%d,\n"
L"将它的高字节和低字节分离,得到%d和%d,\n"
L"再将每个字节减去0xA0,就得到其实际的区位码,%d,%d\n"
L"这与我们的猜想%s",
a, b , b >> 8 , b & 0xff , (b >> 8) - 0xA0 , (b & 0xff) - 0xA0,
(52==(b>>8)-0xA0 && 58==(b&0xff)-0xA0) ? L"相符合":L"不符合");
MessageBox(0,str,L"ok",0x30);
return 0;
}
这个我试过了,不管是在VC2008还是VC6,只需要新建Console Application项目,然后直接新建一个C++源文件然后贴进代码就可以了,完全不需要设置什么。
我来回复