C 源文件内的中文

来源:互联网 发布:手机淘宝好评截图 编辑:程序博客网 时间:2024/06/05 03:32

C 源文件内的中文(1)

1. 目的

Qt中老有人问起汉字编码的问题,想整理一下,可想来想去还是应该从C语言开始。 整理整理自己的思路,有时会突然觉得自己理解的不少了,可是一旦准备写出来时,总会突然间变得没有头绪。

wchar_t 与 char 的一点区别:

  • 就像名字暗示的:
    • wchar_t (wide char type)是多字节,char是单字节(最多256个字符)
    • wchar_t 中存放的是 Unicode,而 char 多字节字符完全没概念。
  • 源文件内
    • 使用 wchar_t 时,编译器负责将本地编码的文字转成Unicode
    • 使用 char 时,编译器一般直接将本地编码的字节流存入char串中
  • 输出时
    • 只需知道客户端本地编码,即可将 wchar_t 从Unicode转成正确的编码流
    • 而char串,则需要本地编码和char串中使用的编码一致。
2. char

严格说来C语言是没有字符串的,有的只是字符数组 char[],以及指针 char *(暂不考虑宽字符 wchar_t )。

首先,我们对 下面的a、b 是完全等价的 这一说法,应该是没有任何异议的。

#include <stdio.h>
int main()
{
char a [] = "ABCD";
char b [] = "\x41\x42\x43\x44";
printf("a:%s b:%s", a, b);
return 0;
}

但是,当涉及到中文时,问题变得相当复杂了(为了能简单些,下面只限制在UTF-8和GBK范围内讨论这个问题)

2.1. "我是汉字"究竟是什么东东

char 只有8位(最多256个字符),它不可能存储汉字的,能存储只是编码后的一个一个的字节

看看这个例子:这个字符串的长度究竟是多少呢?

#include <stdio.h>
#include <strings.h>
int main()
{
char a [] = "我是汉字";

printf("length of a: %d\n", strlen(a));
return 0;
}

将该文件分别保存为GBK、带BOM的UTF8、不带BOM的UTF8 这3种个是,然后分别用MSVC中的cl 和 Mingw 中的 gcc 进行编译 看一下结果:

  • 源文件编码

    编译器

    结果

    GBK

    cl

    8

    gcc

    8

    UTF8(带BOM)

    cl

    8

    gcc

    12

    UTF8(不带BOM)

    cl

    12

    gcc

    12

我们都知道 这4个汉字对应的GBK和UTF-8编码分别是:

"\xce\xd2\xca\xc7\xba\xba\xd7\xd6"
"\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97"

也就是说:

  • 源文件编码

    编译器

    "我是中文" 内存中编码

    GBK

    cl

    GBK

    gcc

    GBK

    UTF8(带BOM)

    cl

    GBK

    gcc

    UTF8

    UTF8(不带BOM)

    cl

    UTF8

    gcc

    UTF8

这似乎是太乱了,不是么?其实很简单:

  • 对 gcc 来说,源文件保存为gbk,就是gbk,保存为utf8就是utf8。没有任何特殊处理。
  • 对 cl 来说,只要你的文件带BOM,就转码成gbk。不带BOM的不做任何特殊处理。
2.2. BOM
  • cl

对cl编译器,源文件可以采用带BOM的utf8,带BOM的utf16等等编码。编译器都会自动转码成gbk!!。

也就是说,你保存成BOM的utf8或utf16,和保存成gbk都是一样的。

  • gcc

前面的实验其实是mingw32-gcc得出的,其实linux下,带BOM的utf8文件编译器是不认的。

3. wchar_t

前面说了 char 不懂中文,于是 C 语言弄了个可以懂中文的 wchar_t。说它懂中文,是因为它可以将一个汉字作为1个字符来处理,而不想char那样,当做2或3个单字节字符处理。但这个东西也不是很好用。

不过事实应该还算好吧,主流的编译器都将其实现为了Unicode,尽管Windows下用的16位的UCS2(UTF-16),而Linux下用的32位的UCS4(UTF-32)。我们就可以简单地认为 wchar_t 存放的就是 Unicode 。

现在是这么一个情况了:

  • 我们的源代码保存时采用的是 UTF8 或 GBK,而 wchar_t 存放的是Unicode,这中间需要编译器来完成一个转换。于是,不同的编译器又各显神威了,唉。
  • 无论我们将结果输出到终端(控制台)或写入到文件,都需要将 Unicode 转换成字节流(UTF8、GBK或其他),这个转换是由 locale 控制。
3.1. 源代码的编码

由于 wchar_t 存放的是Unicode,而源代码所用的编码可能各种各样,一个问题摆在眼前了,编译器对这各种编码都能正常工作吗?

在Windows下做个试验看看:

#include <stdio.h>
#include <wchar.h>
#include <locale.h>
int main()
{
wchar_t a [] = L"我是汉字";

setlocale(LC_ALL, "");
wprintf(L"a:%ls", a);
return 0;
}
  1. 保存为GBK编码,用Mingw gcc和 VS cl 分别编译运行: cl 工作正常,gcc报错:非法的字节序
  2. 用记事本另存为UTF8格式,重新编译运行: cl 与 gcc 均正常
  3. 用16进制编辑器将UTF8文件的开头的3字节的BOM删除,重新编译运行: cl 乱码,gcc正常

列成表格:

  • 源文件编码

    编译器

    "我是中文" 内存中编码

    GBK

    cl

    成功转成Unicode

    gcc

    失败,编译不过

    UTF8(带BOM)

    cl

    成功

    gcc

    成功

    UTF8(不带BOM)

    cl

    失败,输出乱码

    gcc

    成功

3.2. 发生了 什么

其实很简单:

  • 对 cl

只要源代码带BOM,直接转unicode。如果不带BOM,则将其作为gbk编码转换到unicode。

于是,当我们用不带BOM的utf8格式时,cl将其按照gbk解码,最后得到长度为6的字符串。

  • 对 gcc

默认将所有的字符串都按照utf8解码。在上面,当源代码保存为gbk时,它按照utf8解码,解码出错。

3.3. gcc

前面说的都是默认情况,其实gcc是支持其他编码的,只不过需要通过选项来指定。比如对gbk

gcc main.c -finput-charset=gbk -o main

是没有问题的。(Mingw4.4有bug,linux下的gcc及tdm-gcc没有问题)

3.4. 转义字符

同本文一开始一样,我们对下面的程序中的a与b的完全等价性是应该没有任何异议的:

#include <stdio.h>
#include <wchar.h>
#include <locale.h>
int main()
{
wchar_t a [] = L"ABCD";
wchar_t b [] = L"\x41\x42\x43\x44";

setlocale(LC_ALL, "");
wprintf(L"a:%ls b:%ls", a, b);
return 0;
}

那么,汉字的情况的,能和前面char部分的提到的那样:a还等价于 b1 或 b2 中的一个么?

wchar_t a [] = L"我是汉字";
wchar_t b1 [] = L"\xce\xd2\xca\xc7\xba\xba\xd7\xd6";
wchar_t b2 [] = L"\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97";

显然不行了,同L"\x41\x42\x43\x44"一样,现在b1是长度为8的宽字符串,b2是长度为12的宽字符串(每一个代表一个Unicode字符,同下)。

汉字该如何呢? 其实同char中指定ASCII码是一样的,只不过这次换成了Unicode(UTF16)编码:

wchar_t a [] = L"我是汉字";
wchar_t b1 [] = L"\x6211\x662f\x6c49\x5b57";
wchar_t b2 [] = L"\u6211\u662f\u6c49\u5b57";

C 源文件内的中文(2)

4. locale

当我们使用 wchar_t 时,就无法回避locale问题了。因为程序内是 unicode,输出时要输出传统的本地字符串,比如big5、gbk等。到底要如何选择呢?

setlocale(LC_ALL, "");

使用"",也就是让系统帮我们做选择:

  • Linux 下,获取locale的顺序是:环境变量 LC_ALL,每个单独的locale分类LC_*,最后是 LANG 变量
  • windows下,受控制面板中“区域与语言选项”的设置影响。简体中文windows下代码页 936,即 GBK

locale 的具体格式是:

语言_国家地区.编码

在前面的例子中,如果我们显式设置locale为简体中文的gbk编码,则Windows和linux下分别为:

setlocale(LC_ALL, "Chinese_People's Republic of China.936");

setlocale(LC_ALL, "zh_CN.GBK");

感觉上linux下舒服多了。

5. printf

我们都知道printf中的格式控制符是怎么回事,也了解用错会有什么结果,比如:

double a= 100;
printf("%d", a);

printf 是根据格式控制符来解释内存中的内容的,所以,当我们用 char* 和 wchar_t * 时,格式控制就很重要了。不然一个 wchar_t* 直接作为 char * 来解释或翻过来解释,结果你可以想得到的,对吧。

格式控制符

cl

gcc

ls 或 lS

wchar_t

wchar_t

s

char

char

S

wchar_t

char

当我们使用 wprintf 时,gcc的行为不变,但 cl 行为就有点古怪了

格式控制符

cl

gcc

ls 或 lS

wchar_t

wchar_t

s

wchar_t

char

S

char

char

6. 参考
  • http://www.firstobject.com/wchar_t-string-on-linux-osx-windows.htm

  • http://codingdao.com/wp/post/c-stdlib-setlocale-usage-note/

  • http://blog.csdn.net/lovekatherine/archive/2007/11/06/1868724.aspx



原创粉丝点击