大部分C语言教材对中文字符的处理讳莫如深,甚至只字不提,导致很多初学者认为C语言只能处理英文,而不支持中文。其实C语言是一门全球化的编程语言,它支持世界上任何一个国家的语言文化,包括中文、日语、韩语等。
微软编译器使用两个字节来存储 UTF-16 编码的字符,虽然不能囊括所有的 Unicode 字符,但是也足以容纳全球的常见字符了,基本满足了软件开发的需求。使用两个字节存储的另外一个好处是可以节省内存,而使用四个字节会浪费 50% 以上的内存。 你看,不同的编译器可以使用不同的整数类型。如果我们的代码使用 unsigned int 来存储宽字符,那么在微软编译器下就是一种浪费;如果我们的代码使用 unsigned short 来存储宽字符,那么在 GCC、LLVM/Clang 下就不够。为了解决这个问题,C语言推出了一种新的类型,叫做 wchar_t。w 是 wide 的首字母,t 是 type 的首字符,wchar_t 的意思就是宽字符类型。wchar_t 的长度由编译器决定:
中文字符的存储
正确地存储中文字符需要解决两个问题。1) 足够长的数据类型
char 只能处理 ASCII 编码中的英文字符,是因为 char 类型太短,只有一个字节,容纳不下我大中华几万个汉字,要想处理中文字符,必须得使用更长的数据类型。一个字符在存储之前会转换成它在字符集中的编号,而这样的编号是一个整数,所以我们可以用整数类型来存储一个字符,比如 unsigned short、unsigned int、unsigned long 等。2) 选择包含中文的字符集
C语言规定,对于汉语、日语、韩语等 ASCII 编码之外的单个字符,也就是专门的字符类型,要使用宽字符的编码方式。常见的宽字符编码有 UTF-16 和 UTF-32,它们都是基于 Unicode 字符集的,能够支持全球的语言文化。在真正实现时,微软编译器(内嵌于 Visual Studio 或者 Visual C++ 中)采用 UTF-16 编码,使用 2 个字节存储一个字符,用 unsigned short 类型就可以容纳。GCC、LLVM/Clang(内嵌于 Xcode 中)采用 UTF-32 编码,使用 4 个字节存储字符,用 unsigned int 类型就可以容纳。 对于编号较小的字符,UTF-16 采用两个字节存储;对于编号较大的字符,UTF-16 使用四个字节存储。但是,全球常用的字符也就几万个,使用两个字节存储足以,只有极其罕见,或者非常古老的字符才会用到四个字节。微软编译器使用两个字节来存储 UTF-16 编码的字符,虽然不能囊括所有的 Unicode 字符,但是也足以容纳全球的常见字符了,基本满足了软件开发的需求。使用两个字节存储的另外一个好处是可以节省内存,而使用四个字节会浪费 50% 以上的内存。 你看,不同的编译器可以使用不同的整数类型。如果我们的代码使用 unsigned int 来存储宽字符,那么在微软编译器下就是一种浪费;如果我们的代码使用 unsigned short 来存储宽字符,那么在 GCC、LLVM/Clang 下就不够。为了解决这个问题,C语言推出了一种新的类型,叫做 wchar_t。w 是 wide 的首字母,t 是 type 的首字符,wchar_t 的意思就是宽字符类型。wchar_t 的长度由编译器决定:
- 在微软编译器下,它的长度是 2,等价于 unsigned short;
- 在GCC、LLVM/Clang 下,它的长度是 4,等价于 unsigned int。
' '
包围,例如
'B'
、
'@'
、
'9'
等;但是,这样的字符只能使用 ASCII 编码,要想使用宽字符的编码方式,就得加上
L
前缀,例如
L'A'
、
L'9'
、
L'中'
、
L'国'
、
L'。'。
注意,加上
L
前缀后,所有的字符都将成为宽字符,占用 2 个字节或者 4 个字节的内存,包括 ASCII 中的英文字符。下面的例子演示了如何存储宽字符(注意引入 头文件):
wchar_t a = L'A'; //英文字符(基本拉丁字符)wchar_t b = L'9'; //英文数字(阿拉伯数字)wchar_t c = L'中'; //中文汉字wchar_t d = L'国'; //中文汉字wchar_t e = L'。'; //中文标点wchar_t f = L'ヅ'; //日文片假名wchar_t g = L'♥'; //特殊符号wchar_t h = L'༄'; //藏文
在以后的编程中,我们将不加
L
前缀的字符称为窄字符,将加上
L
前缀的字符称为宽字符。窄字符使用 ASCII 编码,宽字符使用 UTF-16 或者 UTF-32 编码。
宽字符的输出
putchar、printf 只能输出不加L
前缀的窄字符,对加了
L
前缀的宽字符无能为力,我们必须使用 头文件中的宽字符输出函数,它们分别是 putwchar 和 wprintf:
- putwchar 函数专门用来输出一个宽字符,它和 putchar 的用法类似;
- wprintf 是通用的、格式化的宽字符输出函数,它除了可以输出单个宽字符,还可以输出宽字符串(稍后讲解)。宽字符对应的格式控制符为
%lc
。
setlocale(LC_ALL, "zh-CN");
在 Linux 和 Mac OS 下请写作:
setlocale(LC_ALL, "zh_CN");
setlocale 函数位于 头文件中,我们必须引入它。下面的代码完整地演示了宽字符的输出:
#include #include int main(){ wchar_t a = L'A'; //英文字符(基本拉丁字符) wchar_t b = L'9'; //英文数字(阿拉伯数字) wchar_t c = L'中'; //中文汉字 wchar_t d = L'国'; //中文汉字 wchar_t e = L'。'; //中文标点 wchar_t f = L'ヅ'; //日文片假名 wchar_t g = L'♥'; //特殊符号 wchar_t h = L'༄'; //藏文 //将本地环境设置为简体中文 setlocale(LC_ALL, "zh_CN"); //使用专门的 putwchar 输出宽字符 putwchar(a); putwchar(b); putwchar(c); putwchar(d); putwchar(e); putwchar(f); putwchar(g); putwchar(h); putwchar(L'\n'); //只能使用宽字符 //使用通用的 wprintf 输出宽字符 wprintf( L"Wide chars: %lc %lc %lc %lc %lc %lc %lc %lc\n", //必须使用宽字符串 a, b, c, d, e, f, g, h ); return 0;}
运行结果:
A9中国。ヅ♥༄Wide chars: A 9 中 国 。ヅ ♥ ༄
宽字符串
给字符串加上L
前缀就变成了宽字符串,它包含的每个字符都是宽字符,一律采用 UTF-16 或者 UTF-32 编码。输出宽字符串可以使用 头文件中的 wprintf 函数,对应的格式控制符是
%ls
。下面的代码演示了如何使用宽字符串:
#include #include int main(){ wchar_t web_url[] = L"http://c.biancheng.net"; wchar_t *web_name = L"C语言中文网"; //将本地环境设置为简体中文 setlocale(LC_ALL, "zh_CN"); //使用通用的 wprintf 输出宽字符 wprintf(L"web_url: %ls \nweb_name: %ls\n", web_url, web_name); return 0;}
运行结果:
web_url: http://c.biancheng.netweb_name: C语言中文网其实,不加
L
前缀的窄字符串也可以处理中文,它和加上了
L
前缀的宽字符串有什么区别呢?我们将在下节《
C语言到底使用什么编码?谁说C语言使用ASCII码,真是荒谬!
》中详细讲解。