0%

VC中的字符编码

先理一下基本的概念,在VC中字符编码有ANSI(也称为多字节字符集)和UNICODE(宽字节字符集)两种。ANSI是指本地编码,在中文环境下就是指GBK编码,而UNICODE编码、在VC中使用的是UTF-16。 同时,说到“编码”,一个是指源码字符集(the source character set),这个好理解,就是指cpp hpp h等源代码文件的编码;另一个是指执行字符集(the execution character set),例如“const char *str="中文"”,这个str指向的内存中的字符编码就是执行字符集了。 注意在本文中,“编码”和“字符集”我会混用,反正指代的含义是同一个。

那么先总结第一点:在VC中处理源文件时,只有UTF-16(BE,LE均可,有无BOM均可)、UTF-8(带BOM)的源文件会被编译器自动识别文件编码, 其它格式的文件(包括不带BOM的UTF-8)统统视为ANSI编码处理

所以如果在无BOM的UTF-8源文件中写中文字符串,VC会把UTF-8字符串当作GBK字符串来处理,这时一般会产生一个warning:“warning C4819: 该文件包含不能在当前代码页(936)中表示的字符”。后果就是字符串面目全非,大概就是“手持两把锟斤拷”了吧......  

以为这样就完了?用带BOM的UTF-8源文件就行了? 远远没有!  第二点来了:为了兼容老版本的VC,在没有声明执行字符集(具体怎样声明见下文第三点)的情况下,即使源文件是UTF-16、UTF-8(带BOM),VC会将字符常量从源文件的编码转换为ANSI编码,同时使用ANSI作为执行字符集

假设你已经使用了UTF-8(带BOM)编码的源文件,那么实际情况如下:

1
2
3
4
5
/**
* 将源文件中的"中文"从源文件编码(如UTF-8)转换成ANSI编码方式,即GBK。
* 此时str有5个字节,每个汉字2个字节(GBK编码),1个结束符。
**/
const char str[] = "中文";

所以有了第三点:对于UTF-8(带BOM)的源文件,在声明使用UTF-8编码的情况下,VC才会使用UTF-8作为执行字符集。这才是我们想要的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//下面这几句是重点!
#if defined (_MSC_VER) && _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif

#include <stdio.h>

int main(int argc, char *argv[])
{
const char str[] = "中文"; //此时str中的字符使用UTF-8编码,str内存为7个字节,每个汉字3字节 + 1个结束符
for(int i = 0; i < sizeof(str); ++i)
{
printf("0x%x ", str[i]&0xFF);
}

return 0;
}

最后一点来了:对于 “_T()”、“L” 这两个宏(这里只讨论在UNICODE下的情况),无论源文件编码、无论是否声明execution_character_set,它们都会安全地声明UNICODE(即UTF-16)编码的字符串常量(注意是wchar_t类型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#if defined (_MSC_VER) && _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif

#include <stdio.h>

int main(int argc, char *argv[])
{
/**
* 此时str使用UTF-16编码,占6个字节内存,每个字符(wchar_t)2个字节(包括结束符)
**/
const wchar_t str[] = L"中文";

return 0;
}

可以发现UTF-8在表示一个字符时有可能占用1到3个字节,在表示中文时是占用3个字节的,而UTF-16始终是使用2个字节来表示。 因此在存储中文的情况下,UTF-16占用的内存是更小的。但是可以发现UTF-8是完全“兼容”ASCII的(即对于只使用ASCII字符的UTF-8字符串、每个字节都是一个ASCII字符。另外GBK也是兼容ASCII的),而UTF-16由于固定使用2个字节因此可以说是不兼容ASCII的。  

最后再提一下Qt吧(本文中指Qt5),Qt中的字符串也使用UTF-16编码存储,所以每个QChar占2个字节。 同时Qt5默认执行字符集是UTF-8,也就是Qt认为字符串常量的编码是UTF-8的。所以当写下代码:QString str = "中文"  时,赋值操作符实际上调用了QString的 fromUtf8() 方法,将UTF-8编码的字符串常量转换为QString内部的UTF-16编码。