慢羊羊的空间

无为,无我,无欲,居下,清虚,自然

详细解释为什么写程序要用 Unicode 字符集

微软在 VC6.0 的时候,创建项目的默认字符集是 MBCS。从 VC2002(就是 VC7.0)开始,默认字符集就变为了 Unicode,直到今天。

本文就讲解编程中 Unicode 与 MBCS 字符集的区别,即 wchar_t 和 char 字符(串)的区别。

通过示例看区别

注:

  1. 实验环境 Windows 7 + VC2010 Express + EasyX_20200109(beta)。(在 Win10 下可以得到同样的结果)
  2. VC2010 Express 设置项目字符集的方法:在 Solution Explorer 右击项目 -> Properties,打开的属性窗口中,左侧选择 Configuration Properties / General 分类,右边将 Character Set 设置为 Use Unicode Character Set 即表示将项目设置为 Unicode 字符集,设置为 Use Multi-Byte Character Set 即表示将项目设置为 MBCS 字符集。
  3. 因为代码中涉及到英文中文韩文字符,需要将 .cpp 文件以 utf-8 编码保存。方法:File -> Save xxx.cpp As...,在文件另存为对话框里,“保存”按钮右边有一个向下的箭头,点击后选择“Save with Encoding...”,同意覆盖原文件,然后 Encoding 选择 Unicode (UTF-8 with signature) - Codepage 65001,Line endings 不用调整,确定保存即可。

两个示例的具体设置与代码

第一个示例程序:

项目名称 TestUnicode
项目类型:Win32 Console Application
项目字符集:Unicode
完整代码如下:

#include <graphics.h>
#include <conio.h>

int main()
{
	wchar_t s[] = L"厉害了我的国";

	initgraph(640, 480);
	settextstyle(24, 0, L"微软雅黑");
	outtextxy(10, 30, s);
	_getch();
	closegraph();

	return 0;
}

第二个示例程序:

项目名称 TestMbcs
项目类型:Win32 Console Application
项目字符集:MBCS
完整代码如下:

#include <graphics.h>
#include <conio.h>

int main()
{
	char s[] = "厉害了我的国";

	initgraph(640, 480);
	settextstyle(24, 0, "微软雅黑");
	outtextxy(10, 30, s);
	_getch();
	closegraph();

	return 0;
}

执行效果

在中文系统下的执行 TestUnicode.xe 和 TestMbcs.exe,效果没有任何区别,如下图所示:

现在将系统切换为英文。操作步骤如下图所示(以英文版 Win7 系统为例):

然后再次执行 TestUnicode.exe 和 TestMbcs.exe,执行效果如下图所示:

也就是说,即便这个美国用户懂中文,也看不到 MBCS 字符集程序里面的中文。

有些同学可能会说,我的程序只给中文用户使用。那么我们试着修改一下程序,把字符串“厉害了我的国”修改为“Computer 翻译为韩文是 컴퓨터”,然后在中文 Win7 系统下执行,效果如下:

PS:暂时先忽略 TestMbcs 项目中 VC 的警告:

main.cpp(7): warning C4566: character represented by universal-character-name '\uCEF4' cannot be represented in the current code page (936)

可以看到:

如果你的程序使用 MBCS 字符集,那么在其他语言的计算机上运行,会无法正常显示。如果你的程序中使用了多语言(例如韩文),那么即便在中文语言的计算机上,也无法正常显示。

原理解释

从 ASCII 码说起吧。

ASCII 码规定了每个字符一个字节,前 128 个属于常规 ASCII 码,后 128 个属于扩展 ASCII 码。常规 ASCII 码里面含有英文大小写字母、阿拉伯数字、常见标点符号等。扩展 ASCII 码里面是一些不常用的字符。

于是在过去,想要表示多语言的时候,就利用了扩展 ASCII 码不常用的特点,将两个连续的扩展 ASCII 码表示成其它语言。

例如中文的 GB2312 编码,将汉字分成 94 个区和 94 个位,区和位分别使用了扩展 ASCII 码的 161~254 这个范围。“中国”两字的区位码分别是 5448 2590,那么可以构造出这样的程序(暂时忽略编译警告):

// 请设置项目字符集为 MBCS
#include <graphics.h>
#include <conio.h>

int main()
{
	// “中”的区位码是 54 48,“国”的区位码是 25 90
	char s[] = {160 + 54, 160 + 48, 160 + 25, 160 + 90, 0};

	initgraph(640, 480);
	settextstyle(24, 0, "微软雅黑");
	outtextxy(10, 30, s);
	_getch();
	closegraph();

	return 0;
}

执行这个程序,可以成功的输出“中国”两个字。

这就是在 MBCS 字符集下中文的表示形式。

其他语言类似,比如韩文的 EUC-KR 编码(过去叫 KSC5601 编码),也用的两个扩展 ASCII 码,并且范围也是 0xA1-0xFE。台湾地区的 BIG5 编码也覆盖了这个范围。那么问题来了:两个扩展 ASCII 码,究竟表示中文、韩文还是繁体中文或者别的语言?

这取决于操作系统的系统区域(locale)设置,
当 locale 设置为简体中文,那么两个连续的扩展 ASCII 码就会根据 GB2312 编码解析;
当 locale 设置为韩文,那么两个连续的扩展 ASCII 码就会根据 EUC-KR 编码解析。

根据这个多语言显示原理可以得出如下结论:

  1. 系统区域设置错误,MBCS 编码的字符串就无法正确显示。
  2. MBCS 编码无法实现多语言混合显示。

当编码与 locale 不匹配的时候,就会出现我们常说的“乱码”问题。于是,Unicode 编码,就是为了解决这个问题来的。

Unicode 编码整合了全世界所有语言的文字,任何语言的任何一个文字,在 Unicode 编码中都有唯一的值对应。因此不再需要“系统区域(locale)”了,也就不会产生“乱码”了。

就像前面的实验看到的那样,采用 Unicode 编码的程序,无论是同时显示多少种语言,无论在什么语言的操作系统上执行,都可以正常显示。

(完)

分享到