个人理解的:C和编码的关系

icoding 2011-09-23 04:45:46
汉字的编码分为三类:中国的GB系列,台湾的big5码,unicode。

1.GB
先说GB系,最早的编码是GB2312,是中国的国家汉字编码标准,GB2312可以兼容ASCII码,ASCII码字符在GB2312里仍然是一个字节,而汉字字符是两个字节,那么他们怎么区分呢?因为标准的ASCII码包含128个字符,字符值最高是127(0111 1111)因此字节最高位为0,而GB利用了这一个特点,将表示汉字的两个字节最高为置1,那么当程序读取GB2312编码的文件时,字节最高位为0的字节就是ASCII码字符,字节最高位为1并且下一个字节最高位也是1则这两个字节定义了一个汉字字符(如果不是这样的,那么就说明文件被破坏了,无法正常读取)这种方法虽然很好的解决了和ASCII码兼容的问题,但也有一个缺陷,因为两个字节的高位已经用做标识符,能表示的汉字字符个数也减少了(仅能表示2^14=16384个字符)所以在GB2312里只收录了7000多个常用汉字字符,之后的GBK等后续发布的编码进一步扩充了收录的字符个数(我看到的资料上这么说,不过这里有一个问题?扩充字符个数的难度在于要扩大字节数,那么GBK还是两个字节的大小吗?)

2.BIG5
我的理解里台湾的big5码和咱们的gb2312是一套类似的方案

3.UNICODE
说了半天,我们的主角UNICODE终于要出场了,说他是主角原因有两点:1)UNICODE的设计目标是覆盖这个星球上所有的语言,使用他理论上可以在任何一台支持UNICODE的机器上正常显示 2)UNICODE已经成为了标准了吧,windows和linux内部都是以unicode存储字符,其他编码输入时都要转成unicode,不过unicode里的术语也不是一般的多,UCS2,UCS4,UTF16,UTF16BE,UTF16LE,UTF32,UTF8,UTF7,说实话我看这么多东西的时候已经晕了,一个一个过吧

UCS2,UCS4 UCS是Universal Character Set的缩写
说的是表示一个汉字字符的字节个数,UCS2是两个字节,UCS4是4个字节,UNICODE不像GB系用一个字节表示ASCII码,因此即使是英文字母再UNICODE里也存储为2个字节,好处是就不许要浪费位来区分汉字和英文字母了,因此UCS可以表示2^16=65536字符,这比GB2312能表示的字符个数强太多了,不过貌似搞UNICODE的那帮人觉得6万多个字符还是不够,所以又搞了一个4字节的版本,叫UCS4,那么现在可以表示的字符是 2^32=4294967296(等一下,我脑子笨,让我数一下先,是42亿,囧,这下怎么都该够了吧)关于这点,我有个很好的例子可以说明一下,C标准库有一个头文件wchar.h,里面定义了一个类型是wchar_t,如果我们sizeof一下的话,发现在windows平台上是2个字节,而 linux上是4个字节,因此说明 windows平台上玩的是UCS2,而linux则直接上了UCS4,大家不妨玩一下,应该可以应证我说的

好了,那么UCS2和UCS4之间怎么转换?看起来UCS4是兼容UCS2的,因为同一个字符UCS4的2个低字节内容就是UCS2两个字节里的值,而2个高字节里全是0,因此UCS4去掉两个高字节就是UCS2编码了

实验:
在linux上建立一个文件,用gedit打开,输入内容,选择菜单文件/另存为,选择编码类型(可以增加删除UCS2和UCS4编码)分别存成UCS2和UCS4,打开shell窗口输入od -h ucs2.txt 和 od -h ucs4.txt 察看字节编码
注意UCS2和UCS4的文件都有一个数据结束符(UCS2是000A UCS4是0000 000A)

UTF16和UTF32又是啥?UTF其实是unicode transfer format就是对UCS2,UCS4进行编码然后用于传输的格式
UTF16 简单的对UCS2进行了一下简单处理,例如在数据前添加FF FE两个字节表示这是UTF16并且它是小尾的(FE FF是大尾,事实上仅仅是UTF16 还要多种处理方式,小尾是UTF-16LE,大尾是UTF-16BE, FF FE称为BOM),同样UTF32对应的是UCS4,但是显然这样的传输格式其实并不环保,写过网络通信的人都知道其实我们要降低数据传输量,因此UTF8就出来了,UTF8是一种变长编码,范围是1~6个字节,具体怎么样,暂时不描述,等将来我自己搞的比较明白还有点时间再说,UTF7还没看,不知道是啥意思

GB和unicode之间的转换?
GB和unicode 应该是没什么关系的两种版本,同一个汉字再两个编码里的值绝对不一样,但是他们是可以转换的
没找到太多的资料(或者有,我没看进去,那么效果有没有也差不多)但是看起来应该是没有问题,怎么转换一定还是立足于某个规律,而且这个规律是稳定的,就像数学归纳法一样
第i个GB字符 可以转为 UNICODE同样的字符
第i+1个字符 也可以
第i+j个可以

那么就是可以的

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

概述
计算机原理里关于汉字处理是这么描述的:计算机通过输入设备读入汉字(输入码)经过转换程序,转换为计算机内码,再经过字模库(字型码),输出到输出设备,个人理解所谓的内码就是linux选择的unicode,其他类型在存入数据时都要转成UNOCODE

为什么要设置locale?
个人认为locale.h是C语言标准库里比较晦涩的库之一(另一个是signal.h)这个库里定义了一个setlocale函数,该函数可以指定地区和编码,那么为什么要指定编码呢?我们知道在linux上我们可以再shell里通过命令运行程序,shell的编码设定其实就是C程序的环境变量,调用setlocale其实是对程序运行环境的编码做一个假设,例如将编码设定为GB2312,使用C语言标准库的fgetws等宽字符版本的函数去读写文件时,会将文件的编码设定为GB2312,尝试从GB2312转换成为UNICODE(windows上是UCS2,Linux上是UCS4)输出时再次尝试从UNICODE转换成为GB2312(注意:这种设动并不会真正影响到环境的编码设定)一旦读取的程序不是GB2312的,又或者shell不是GB2312而是unicode,那么wprintf就会输出乱码

char *setlocale(int category, const char *locale);
疑问:category在相关的书籍上都可以查阅到,但是我们怎么知道到底都有那些locale可以用呢?在linux上可以使用locale,locale -m命令查看系统正在使用的编码,支持的编码都有什么,一般来说汉字编码可以设定为zh_CN.gb2312,zh_CN.utf8它的规则是<语言>_<地区>.<字符集编码>,因此可以这样调用:
char *p = setlocale(LC_ALL,"zh_CN.utf8");
assert(p!=NULL);

为什么不设定loacle,printf("中文")也能正常输出
我们说了中文OS下的文件有多种编码格式,源文件也不例外,linux下文件(包括源文件)都被存为utf8,因此源文件中的汉字其实是若干个unicode字节,在编译时,这几个字节原封不动的进入编译后的指令,执行时相当于直接放到变量里去,输出时,因为shell默认也是utf8,数据和环境编码一致,因此可以正常输出,不仅如此,如果,从文件里读取到的文本编码也是utf8那么这些字符串printf的时候也可以正餐输出,windows也是类似情况

#include <stdio.h>

#include <locale.h>

#include <wchar.h>



void print_wc_val(wchar_t c)

{

char *p = (char*)&c;

wprintf(L"%x %x %x %x \n",p[0],p[1],p[2],p[3]);

}



int main()

{

int i;

char *code;

FILE *fp,*fp2;



code = setlocale(LC_ALL,"zh_CN.utf8");//zh_CN.gb2312 //zh_CN.utf8

wprintf(L"%s\n",code);



wchar_t s[100];

fp = fopen("1.txt","r");

fgetws(&s,100,fp);

wprintf(L"%ls\n",&s);



for(i=0;i<wcslen(s);i++)

{

print_wc_val(s[i]);

}



fp2 = fopen("r.txt","w");

fwprintf(fp2,L"%ls\n",s);

fclose(fp2);

return 0;

}

...全文
397 19 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
赵4老师 2011-09-26
  • 打赏
  • 举报
回复
参考以下文件内容:
C:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\cvt\big5
C:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\cvt\gb2312
C:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\cvt\gb12345
C:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\cvt\utf8
C:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\cvt\utf16
C:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\cvt\utf8_utf16
赵4老师 2011-09-26
  • 打赏
  • 举报
回复
VC调试(TC或BC用TD调试)时按Alt+8、Alt+6和Alt+5,打开汇编窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应内存和寄存器变化,这样过一遍不就啥都明白了吗。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
赵4老师 2011-09-26
  • 打赏
  • 举报
回复
通常UTF8表示一个汉字需要3个字节
ouen333 2011-09-26
  • 打赏
  • 举报
回复
顶,扫盲贴
zhongyuanceshi 2011-09-25
  • 打赏
  • 举报
回复
不错,记的刚工作时有用到过编码,当时有研究过一段时间,收藏了
AndyZhang 2011-09-25
  • 打赏
  • 举报
回复
分享了
qq120848369 2011-09-25
  • 打赏
  • 举报
回复
讲得不错,linux里一切都是Utf8,除非特意做过编码转换iconv,否则都应该规定locale为utf8,保证读写正确。
AnYidan 2011-09-24
  • 打赏
  • 举报
回复
收藏,慢慢看
gweat_329 2011-09-24
  • 打赏
  • 举报
回复
好东西。。。。。
icoding 2011-09-24
  • 打赏
  • 举报
回复
UPDATE:

把这个帖子贴到了CHINAUNIX和CSDN,很多人给了评论,感谢(在CHINAUNIX顶到首页了,开心,嘿嘿)也蛮有启发的,整理如下:

1) GBK编码仍然是2字节编码,不过第一个字节高位设置为1第二个字节高位用于表示数据,因此GBK能表示30000多个字符

2) 之前说GB编码里的ASCII码如何如何,其实不准确,其实只是GB为了兼容ASCII码,让基本的英文字符和ASCII码保持一致
3) 有回复说为什么可以正常输出printf("中文"),其实是中文OS做的工作,想想也对,printf只是将unicode或者gb2312字节传输给处理程序(是啥不是很清楚?)它完成总字节编码到屏幕打印的工作,那么setlocale实际完成的是什么呢?其实是编码转换,中文编码有多种,我们需要数据的输入的编码是哪种,这样的话才可以采用相应的转换方法转换成为unicode,输出的时候也是如吃

./app > 1.txt
wchar_t cstr[LEN];
...
wprintf(L"%ls\n",cstr);

重定向输出到文件的时候,其实我们就能看见输出的字节到底是些什么当setlocale不一样时,产生的输出也不一样,这样其实说明了setlocale影响了wprintf的编码转换
Roy_Smiling 2011-09-24
  • 打赏
  • 举报
回复
其實我是來打醬油的哦
飞天御剑流 2011-09-24
  • 打赏
  • 举报
回复
printf("中文")也能输出是操作系统的功劳。printf只按照非国际化的默认方式"C",也就是ASCII码,中文两字被printf当作ASCII码(及其扩展字符集)输出到操作系统的IO,操作系统IO再将它们辨识出来。

C++的国际化组件比C更进一步,C只能划分到国家,但C++可以划分到城市。且各种facet以嵌套类的方式包含在locale容器中,提供了比C更灵活的操作方式。
东莞某某某 2011-09-24
  • 打赏
  • 举报
回复
涉及字符集的不多 ,只用多字符和UNICODE , 顶楼主
xujianglun 2011-09-24
  • 打赏
  • 举报
回复
好东西,。。
shdyxm 2011-09-24
  • 打赏
  • 举报
回复
嗯,楼主很用心啊。不过越是通用,废码越多。
Alexander 2011-09-24
  • 打赏
  • 举报
回复
自动排版把表样式弄乱了,重发下:

utf-8 unicode
0xxxxxxx 0x0000 0000-0x0000 007f
110xxxxx 10xxxxxx 0x0000 0080-0x0000 07ff
1110xxxx 10xxxxxx 10xxxxxx 0x0000 0800-0x0000 ffff
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 0x0001 0000-0x000f ffff
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 0x0010 0000-0x03ff ffff
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 0x0400 0000-0x7fff ffff
Alexander 2011-09-24
  • 打赏
  • 举报
回复
utf-8对应ucs2的转换对照表:
utf-8 unicode
0xxxxxxx 0x0000 0000-0x0000 007f
110xxxxx 10xxxxxx 0x0000 0080-0x0000 07ff
1110xxxx 10xxxxxx 10xxxxxx 0x0000 0800-0x0000 ffff
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 0x0001 0000-0x000f ffff
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 0x0010 0000-0x03ff ffff
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 0x0400 0000-0x7fff ffff

以上参照表摘自百度百科

PS:对于纯英文编码,网络传输中用多的不是ASCII,而是其扩展码Latin1,国际标准号为ISO-8859-1。它扩展了ASCII没用的最高位为1的128个字符(0x80-0xff),追加了拉丁语系用到的特殊字母以及重音符号,还加入了一些数学上常用的符号。
harizu76 2011-09-24
  • 打赏
  • 举报
回复
好深奥。要慢慢研究
jackyjkchen 2011-09-23
  • 打赏
  • 举报
回复
GBK不是大陆标准,是M$扩展的标准,后来的国标GB18030是参照GBK的

二字节共有65536个字符位,不需要扩展

太长了,木有看全

70,023

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧