最近工作遇到汉字gb2312转utf-8编码的问题,这种问题以前也遇到过几次,每次遇到都是感觉模模糊糊,似懂非懂,故这次写个博客好好总结下。
一、为什么需要编码
我们都知道计算机采用的是二进制值,我们电脑上的所有数据最终都是用0和1这种二进制表示,像文档、图片、音频、视频等等,他们都是通过各种不同的编码方式生成对应的二进制数据存储在我们的存储设备里。我们文档里的汉字也不例外,下面我们来看个记事本如何保存汉字的实例:
我们新建个记事本,取名test.txt,里面写个汉字“中”,然后选择文件->另存为,在右下角编码选择ANSI(该编码模式中文用的gb2312编码),保存。我们打开vs2003编辑器,依次选择文件->打开->文件->选中test.txt文件->点击在右下角打开按钮里的小三角选择打开方式->选择二进制编辑器->打开。
![](https://i-blog.csdnimg.cn/blog_migrate/1938efd9e7c95c69b9e21983040ee647.png)
打开后我们可以看到如图二里的内容,D6D0就是test.txt文档里中保存在电脑的二进制数值,有人就会问,为啥不是1101011011010000,这是工具是用十六进制表示,用十六进制更方便我们看,在gb2312编码里查找“中”对应的编码也确实是D6D0。
![](https://i-blog.csdnimg.cn/blog_migrate/f81270a5b141c25450fe484f5867fc5b.png)
二、编码简介
几十年前,美国人发明了计算机,但是计算机只能识别0和1这样的数值,那英文字符abc等这些如何保存呢?后来他们想了个办法每个英文字符对应一个数值,比如:a就用二进制1100001表示,十六进制61。他们拿了一个字节来表示这些数据,我们知道一个字节可以表示256个数值。对于英文和一些字符已经绰绰有余,这就是ascii码。但是后来中国引入了计算机,发现中国光简体汉字就有六千多个,一个字节完全不够用啊。对于聪明的中国人,这些根本难不倒我们。我们在ascii码的基础上加了一个字节,用两个字节表示。两个字节可以表示65536个数字,已经完全够我们用了。gb2312是保留ascii编码的,即英文用一个字节编码,汉字用两个字节编码。所以才有了我们说的一个汉字两个字节,一个英文一个字节。但是越来越多的国家都在使用计算机,他们都有自己的语言,于是越来越多的编码出现。这给跨国软件造成了很大的麻烦,太多的编码方式需要兼容到软件里面。于是国际组织又重新定义了一套编码,也是用两个字节表示,包括英文也是。这就是Unicode编码,也叫万国码。
utf-8其实只是Unicode编码的一种实现方式。实现Unicode的方式有多种,比如:utf-7、utf-16等等,但是最常用的是utf-8。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用编码的位。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
比如“中”,其Unicode编码是4E2D,在0000 0800-0000 FFFF之间,采用1110xxxx 10xxxxxx 10xxxxxx模式,4E2D二进制是100111000101101,转化对应的utf-8编码为11100100 10111000 10101101,即E4B8AD
三、C语言中的汉字编码问题
C语言中的汉字编码需要注意两点,一个是编辑代码的编码方式,一个是运行时的编码方式。
编辑代码的编码方式是指源码文件保存在磁盘是采用哪种编码方式。vs2003在文件->高级保存方式->编码里选择。见图三
![](https://i-blog.csdnimg.cn/blog_migrate/df02ace219f1abf4da75da62fb3573c7.png)
一般vs2003默认是GB2312格式,我们新建个项目,再新建个Test.cpp文件,写入一下代码:
void test()
{
char *p = "中";
}
我们用vs2003二进制形式打开该文件,如图四
![](https://i-blog.csdnimg.cn/blog_migrate/0190a2111c798fb0dfdfe607713ea0be.png)
我们对照gb2312编码就能找到红色标注的D6D0就是我们汉字“中”的对应编码。我们在高级保存方式里选择Unicode(big-Endian)编码再保存该文件,再用二进制方式打开该文件,如图五:
![](https://i-blog.csdnimg.cn/blog_migrate/635f47c3981c8360bcf3b1013000a070.png)
我们对应找到“中”在文件里保存的编码变成了4E2D,这与Unicode编码表上的“中”一致。
运行时的编码就是程序运行时保存在内存地址时的编码方式,如果源码用gb2312保存,运行编码方式和源码保存方式是一致的。如果是Unicode和utf-8编码方式会默认utf-8编码。看例子
首先我们先将编码方式设置成gb2312,编辑代码
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
const char szTest[100] = "中";
return 0;
}
我们调试看其反汇编(如果不会汇编可以用printf函数十六进制打印),D6D0正是“中”的GB2312编码
![](https://i-blog.csdnimg.cn/blog_migrate/c95524e8f2a5ba803ce83e6da7a84d92.png)
我们将编辑模式改为Unicode编码,发现其实际是以utf-8方式保存,无论我们是Unicode还是utf-8方式,其运行的编码方式都是utf-8,如图七:
![](https://i-blog.csdnimg.cn/blog_migrate/be50c4db3758c264393224617988317d.png)