字符编码的问题,上大学那会儿就遇到过,一直都是云里雾里,没太搞清楚。最近又遇到了问题,想在C++的控制台上输出Utf-8编码的汉字字节流。尝试了好多次都是乱码,后来花了些时间查查资料,又和同事交流了一下,算是把C++上对于UTF8编码的处理大概摸清楚了。
字符集
先说一个名词:字符集,没听过的先百度一下,其实就是一种将字符编码的格式,像我们常说的ASCII,UTF8,GBK都是常用的字符集。
首先要清楚,从你在编辑器里输入一个UTF8汉字开始,到最终在控制台上显示出来,整个流程涉及到三个概念,分别是源码字符集,执行字符集,解析字符集。
分别解释一下:
源码字符集:就是你的源代码文本文件的字符集,如果你手头有NodePad++这样类似的文本编辑器你可以打开看一下你的字符集,或者用Windows记事本另存为的时候也会显示文本格式。要知道,你的源代码文本文件是以二进制的形式躺在硬盘里的,无论中文英文都一样,当你输入一个汉字后保存关闭,这个汉字就是按照你指定的字符集转换成二进制编码保存下去的,当你在以这个格式打开文件时候,就再按照你指定的字符集把二进制转回来。如果两次使用不同的字符集,也就会出现乱码了。
执行字符集:在C++里 char* str= “我”;执行字符集决定了这行代码在编译器进行编译的时候str存储的字节到底是什么,你可能会说源码字符集不是已经决定了这个”我”的二进制表示了么,没错,但是这个执行字符集就是让你在这里对它再解释一次。比如我源码字符集可能是UTF8的,但是我可以通过执行字符集来让最终ptr存储的是GBK的字节编码。
解析字符集:最终要还原显示这些二进制字节编码的时候,就需要用到它。比如通过printf把前面的str显示到控制台时,这个printf就会按照解析字符集来解析这些字节编码,找到指定字符显示出来。
饶了一圈,好像也不是很乱,但是这里面是有很多坑的。这几个字符集的处理都是跟具体编译器甚至操作系统相关的,不同的编译器是有差别的,我这里只说Windows7系统下VS2013(msvc编译器)的环境。
VS2013中的字符集概念
1.对于源码字符集在VS2013文件->高级保存选项->编码中可以查看设置当前源代码文件的源码字符集。
2.对于执行字符集,VS2013默认根据系统的Locale来决定执行字符集,一般大家都是windows中文系统,Locale是中国,那么就是GBK编码。
3.对于解析字符集,我试了一下,如果没有手动更改的话VS2013的标准输入输出(printf)到命令行也是根据系统Locael决定的,也就是GBK。
案例分析
现在我们就分析一下,假如下面这段源码我们用UTF8格式保存(无Bom).分析一下控制台上显示的结果。
1 char* str= “我”; 2 printf(“%s\n”,str);
1.首先这个代码文件的文本中”我”这个汉字是以E68891三个字节编码的.
2.当编译器编译这段代码时,执行字符集默认是GBK,那么编译器要决定str的字节内容,就要把文本里保存的字节内容转为GBK,这里就有个值得注意的问题,既然要转换到GBK,就需要知道从什么格式转换到GBK,MSVC怎么知道源格式呢?方法只有一个就是分析你的源文件有没有有BOM,要是有就按照BOM它就认为原格式就是BOM指定的格式(不了解BOM可以先百度一下),如果没有BOM他就认为你的源码字符集是Locale关联的。刚才说了我们是用UTF8无BOM格式保存的源文件,所以编译器认为源码文本中的”我”是GBK编码保存的。
3.那从GBK到GBK,MSVC不会进行任何转换,这里有个小问题,提醒一下,这个代码应该是编译不通过的,因为GBK中汉字是2个字节表示的,而UTF8中是三个字节,所以编译器为了凑数会把”我”字后面的双引号给吃掉,转成了两个GBK汉字编码E688,9122(22是引号的UTF8编码),没有引号编译器就会报错,最简单的解决办法就是在在后面在加一个汉字变成偶数个就没问题了。
4.程序运行起来后printf输出到控制台,这时候用到的解析字符集也是GBK的,就会用内存里的E688,9122去GBK字符集里找到对应编码的汉字“鎴?”。这当然就错了。
字符编码可以到这个网站去查询http://www.mytju.com/classcode/tools/encode_utf8.asp
解决方案
这就是我一开始出现的错误,既然知道问题了,那怎么改呢,为了让UTF8编码的源文件中的”我”字可以显示到命令行上,我们需要进行如下分析:
1.首先一定要在编译的时候让str的字节内容是UTF8格式的才行,那就需要让执行字符集是UTF8才行,前面说到MSVC执行字符集是根据Locale来决定的,本来是没法更改的,但是微软后来打了个小不定添加了一个预处理#pragma execution_character_set("utf-8")。来告诉编译器执行字符集设置为UTF8。
2.编译时候进行转换到执行字符集需要知道源码字符集,之前我们是没有带BOM,这导致MSVC认为我们的源文件是GBK编码的,但其实我们是UTF8编码,这就需要我们保存源码的时候改为用UTF8带BOM的格式。这样就不会有问题了。
3.最后要显示出来,既然内存里是UTF8编码,解析肯定也要按UTF8格式来解析,所以我们要把默认的解析字符集从GBK设为UTF8,最简单的方法就是在输出之前调用system(“chcp 65001”);这是命令行设置当前代码页的命令。
这样应该就能正常显示UTF8字符了,不过有个问题就是如果str用cout输出的话,依然是乱码,这个可能是因为cout有自己的解析字符集,不会随着chcp命令改变。这个有待研究,哪位同学知道,可以留言告诉我。再说一点#pragma execution_character_set("utf-8")这个预处理在C++11里已经不再需要了,C++11可以指定字符串字面量的执行字符集了,u8”我”。就这么简单。但是vs2013并不支持这个功能。这篇文章讲述的内容,并不在于如何把一个UTF8格式的C++字面量输出到控制台。而是在于通过这个例子来了解MSVC C++是如何处理UTF8中文字符的。