致编辑:关于标题的翻译——(确定、一定以及肯定)使用了对称原文的众多语气副词,(最最最起码)用词性转换强调原来的(minimum)

日期:2003年10月8日,周三

你是否曾经迷惘于神秘的Content-Type标签? 你知道啦,就是你得放在HTML代码中的那个标签,而你从没太搞明白它到底应该是什么?
你是否曾经收到过保加利亚朋友的电子邮件,而邮件标题却是“???? ?????? ??? ????”?

enter image description here

我沮丧地发现,居然有那么多软件开发人员其实并没有完全理解字符集、编码、Unicode等等这些东西。就在几年之前,FogBUGZ beta版的一名测试人员问它能否处理日文邮件。1日文?他们会收到日文邮件吗?我不知道。不过我还是认真研究了我们用来解析MIME邮件的那个商业ActiveX控件,结果却发现这个控件处理字符集的方式完全是错误的,于是我们干脆写代码先撤销它的错误转换,然后再正确地转换一次。我又研究了另外一个商业代码库,其处理字符集的代码同样是一塌糊涂。我联系上了这个工具包的开发人员,他的反应大概也是爱莫能助。就像很多程序员一样,他只盼着这些问题会自己销声匿迹。

1. .FogBUGZ的简要说明——译者草注

但是,它们不会的!后来我又发现即使流行的网站开发工具PHP都几乎完全无视字符编码问题,它草率地使用8个比特来表示字符,这怎么可能开发出优秀的国际化Web应用呢?!我想,够了,真是受够了致编辑:(web development tool)更多的习惯译法是(Web开发工具),这里考虑到期待受众是更广泛的普通非专业读者,故译成(网站开发工具);(web application)则屈从于(Web应用)的普及性太强;(darned near impossible)怀疑darn是副词darned,因此不用(近乎不可能),而用(无限接近于不可能)表现出作者的话唠调侃属性。
因此,我在此声明:如果你今天还是一位在职程序员,但你却不知道关于字符、字符集、编码及Unicode的基础知识,那么别让我逮到你!一旦被我逮到,我就把你关在潜水艇里剥上六个月洋葱以示惩戒!2致编辑:(working in 2003 我看到的原文是working in the twenty-first centrury )因对照本文写作时间而作改动为(今天还是一位在职程序员);(and I catch you)进行了句式变换以便叙述更流畅;(in a submarine)的表达有所拓展,这是为了省去译者注,让阅读更流畅。我发誓,我一定会那么做的!

2. .剥洋葱时散发的气味呛人难挡,如果关在密封的小空间中,再剥上六个月,那简直惨不堪言——译者草注

哦,再宣布一件事:

这其实并没有那么难。

本文将直截了当地向你灌输那些每位在职程序员都应该知道的东西。关于“纯文本=ascii=每个字符占用8个比特”的论调不仅仅是错误的,而且错得无可救药,如果你仍然这样编程,那你比一位不相信细菌存在的医生也好不了多少。3拜托,请在读完本文之前千万别再写哪怕一行代码!

3. 古代医生类似于巫医,会用到水蛭、咒语,随着科学的发展,现代医学认识到微生物细菌才是致病及治病就医的关键——译者草注

在开始之前,我先打个招呼:如果你凑巧是为数不多的了解国际化的那几个人之一,可能会发现这里的整个讨论有那么一点点过于简化。我其实只是想在这里设立一个最低门槛,好让大家都能理解这是怎么一回事,从而让写出的代码有可能正确处理任意语言,而不仅仅是只能处理那个小得可怜的甚至不含注音字母的英文字符集。致编辑:这里拓展了一些文字以说明(the subset)这个特指子集另外,我再说明一点:字符处理只是创建国际化软件漫漫长征中的一小步,但饭只能一口一口吃,我也只能一点一点写,所以,今天只谈字符集。加了(饭只能一口一口吃)是为了让后面(一点一点写)不那么突兀

溯源之旅

要想搞清楚这些,最简单的方法就是溯古抚今。
你可能觉得我会从古老的EBCDIC(Extended Binary Coded Decimal Interchange Code,扩充二十进制交换码)开始讲起。4放心吧,我不会讲它。EBCDIC跟我们的生活毫无瓜葛。我们也没有必要回溯得那么久远。

4. EBCDIC是一种过渡性字符集,后来有了统一的ASCII,就废弃不用了——译者草注

enter image description here 致编辑:此图可以换成表格并标注各控制字符的含义

在计算机的中古时代,Unix刚刚发明,K&R还在写他们的《C编程语言》5,一切都至简不繁。致编辑:(very simple)译为(非常简单)会失去(简洁、干净利落)的意思EBCDIC正渐渐退出历史舞台。唯一需要考虑的就是当年那些不带注音的英文字母,人们为此搞出了一套名为ASCII的编码,使用数字32至127表示了所有的字符。32是空格,65是字母“A”,等等。用7个比特即可方便地储存这样的数字。那个年代,大多数计算机都使用长度为8个比特的字节,于是一个字节不仅仅能存储所有可能的ASCII字符,而且还空闲了整整1个比特,如果你居心不良,完全可以将它用于自己那不可告人的目的:WordStar用到了这种不够敞亮的小点子,它会点亮那个高位比特以标示单词的最后一个字母,而这限定了WordStar的眼界只能止步于英文。小于32的那些编码叫做不可打印字符,它们通常用于施咒——开个玩笑啦!这些编码用作控制字符,譬如7会让计算机发出哔哔的声音,12会让打印机走纸,然后装入下一张纸。

5. C语言的开山大作,从此开始了C的辉煌征程——译者草注

迄今为止都还不错,但前提是你得说英文。

enter image description here

由于每个字节有多达8个比特的存储空间,所以很多人会想,“啊哈,我们可以把码值在128至255之间的这些编码拿来中饱私囊嘛!”麻烦的地方在于,很多人都同时打着这样的算盘,而他们对这段码值空间内应该放些什么都各怀鬼胎。致编辑:(各怀鬼胎)比(各持己见)、(有着自己的想法)多了些作者的戏谑之意IBM-PC搞出了一种后来被称之为OEM字符集的东西,这种字符集针对欧洲语系提供了一些注音字符和一堆画线字符(横线、竖线、左端带小拐角的横线、上端带小拐角的竖线、……)致编辑:为了补足原文省略号的言外之意,加上了(上端带小拐角的竖线)这句以便让读者自行类推,这些画线字符可以用来在屏幕上绘制出整洁漂亮的条条框框——现在干洗店里仍没淘汰的8088古董电脑上仍然能看到这些线框艺术品。6事实上,美国一旦开始出口电脑,就会萌发臆造出各种不同的OEM字符集,所有这些字符集都会自作主张地使用高端那128个字符。举个例子,编码为130的字符在某些PC上会显示为é,但在销往以色列的电脑上却会显示为希伯来字母ג(这是希伯来语字母表的第三个字母Gimel),于是,当美国人把自己的résumé(个人履历)发往以色列时,送达时却变成rגsumג。很多时候,譬如对俄语而言,甚至存在很多不同的实现高端128个字符的思路,这样即便都是俄语文档也无法放心大胆地相互传阅。致编辑:(reliable interchange)直译为(可靠交换)对非专业读者来说有阅读障碍,故作此变通

6. IBM-PC指IBM推出的个人电脑,因这些电脑销售往不同的国家,故IBM针对不同的国家搞出了不同的字符集,即OEM,Original Equipment Manufacturer,贴牌机器的字符集——译者草注

终于,这场人人得而享之的OEM免费盛宴因ANSI标准而趋于有法可依。根据ANSI标准,所有人都一致认可低端128个编码的实现方案——该方案几乎和ASCII一模一样,但根据居住地的不同,允许存在很多不同的方式来处理编码为128及以上的那些高端字符。这些不同的编码机制就叫做代码页。于是,举例来说,以色列购买的DOS会使用862代码页,而希腊用户会使用737代码页。这些代码页对于128以下的编码完全相同,但自128以上的内容则各不相同——所有那些妙趣横生的字母都放在这里。MS-DOS的多国版本带来了数十种这样的代码页,它们可以处理从英语到冰岛语的所有文字,甚至还有几种“多语种”代码页,可以实现在一台电脑上同时处理世界语和加利西亚语!7哇哦!但是,让诸如希伯来文和希腊文之类的两种文字共处一台电脑仍然是完全不可能的,除非你自己编写定制程序以使用位图方式显示所有内容——因为希伯来文和希腊文对高端编码的解释有所不同,而这要求使用不同的代码页。8

7. 世界语及加利西亚语的渊源及此代码页说明——译者草注
8. 为什么要定制程序及使用位图方式呢?这要从计算机为什么能处理文字说起。录入>查代码页>找字库实现——译者草注

与此同时,亚洲发生的事情甚至更加疯狂,这里需要考虑到亚洲语系动辄存在上千个字符这一事实,而这么多字符是完全不可能挤进8个比特的。为了解决问题,通常会用到一套名为“双字节字符集(DBCS, Double Byte Character Set)”的复杂系统;在这套编码系统中,某些字符会存储为一个字节,其他字符均存储为两个字节。很容易在字符串中作自前向后的移动,但自后向前的移动却无限接近于不可能。9在字符串中前后移动时,不建议程序员们使用s++/s--语句,而是建议调用专用函数,诸如Windows函数AnsiNext/AnsiPrev,这些函数知道如何理清这一团乱麻。

9. 因为自后向前移动时,无法判定是从双字节字符的边界字节开始,还是从这两个字节中段开始——译者草注

然而,大多数人还在自欺欺人地认为,一个字节就是一个字符,一个字符占用8个比特;如果你从来不在不同电脑之间来回倒腾文字,或者你永远只说一种语言,那么你确实可以奉之如圭臬。不过,互联网一出现,自然而然就非常频繁地要把文字从一台电脑转移至另一台电脑,而关于代码页的这团乱麻终于病入膏肓。致编辑:(the whole mess came tumbling down)原来用(一地鸡毛终将尘埃落定),但这与后面Unicode登场这句对不上逻辑。幸运的是,这时已经发明了Unicode。

Unicode

Unicode是一次勇敢的尝试,它致力于建立一个字符集来囊括这个星球上所有有据可查的书写系统,甚至还包括一些生造臆想的文字系统,如克林贡语。10有些人误以为Unicode只是一种长度为16个比特的编码,这里每个字符都占用16个比特,因此一共存在65536个可能的字符。这其实并不对!鉴于这是最通行的Unicode神话之一,如果你也这么想,请别觉得难过。

10. 克林贡语是星际迷航Startrek中某外星种族操持的一种语言——译者草注

事实上,Unicode只是一种不同的思考字符的方式,你必须理解Unicode思考事物的方式,否则这一切毫无意义。
迄今为止,我们一直认为字符会映射成磁盘或内存中储存的比特串:致编辑:(bit)作单位名词为(比特),作一般性名词为(二进制位),另,有无必要以译者注形式普及bit,byte等基本存储单位?

A -> 0100 0001

对于Unicode,字符会映射成一种叫做码点的东西,而码点仍然只是理论上的一种存在。 内存或磁盘中如何表示这种码点呢?这完全是另外一个故事了,这里且按下不表。致编辑:(nuther)是(neither)的变体,目前的表述似乎不够简洁
在Unicode中,字符A就像柏拉图的理想国,它只是不接地气地飘荡于九天之上:

A

这个柏拉图式的A 不同于B,也不同于a,但等同于A、A以及A。致编辑:这里印刷时要注意字体尽量差异明显 Times New Roman字体下的A与Helvetica字体下的A是同一个字符、但不同于小写字符“a”,这种看法似乎没什么异议;但对于某些语系,即使是界定字符是什么这样简单的问题都会引起纷争。致编辑:印刷时这两个A的字体应有所区别 德语字母ß究竟是一个真正的字母?还只是ss的一种变体写法?11 如果词尾字母形状有所变化,它是否变成了另一个字母? 希伯来文认为“是”,而阿拉伯文回答“否”。 不管怎样,在数十年之前,Unicode协会的那些智者就已经解决了这些问题,尽管当时充满了大量外交辞令般的论战,但毕竟现在你无需再担心这个了。(highly political debate)的翻译有待商榷 尘埃早已全部落定。

11. ß这个德语字母正逐渐被废弃,瑞士德语已明确规定用ss来代替ß——译者草注

Unicode协会对各个语系中每个柏拉图式的字符都指派了一个神奇的数字,这个数字的写法形如: U+0639。 这个神奇的数字称作码点。 U+代表着“Unicode”,其后的数字为十六进制数。 U+0639为阿拉伯语字母ع(阿拉伯语字母表的第18个字母Ain)。 英语字母A则对应着U+0041。 使用Windows 2000/XP系统中的charmap(字符映射表)实用工具,或访问Unicode官方网站(http://www.unicode.org),均可找到所有这些字符。 Unicode能定义的字符数没有具体的限制,事实上,Unicode已定义的字符数早就远远超过了65536个,因此,其实并非所有的Unicode字符都能挤成两个字节——不过,这本来就是一个神话哈!
好了,现在假设我们有一个字符串:

Hello

这个字符串在Unicode中对应着以下5个码点:

U+0048 U+0065 U+006C U+006C U+006F

只是一堆码点而已。 其实就是一堆数字。 迄今为止,我们仍未谈及如何在内存中存储这种码点,也从未谈到如何在邮件消息中表示这种码点。

编码

终于该编码登场了。
Unicode的最初编码方案是,“嘿,我们就按一个数字占两个字节来存储这些数字好啦”,正因如此才带来了那个Unicode双字节神话。 于是,“Hello”变成了:

00 48 00 65 00 6C 00 6C 00 6F

对吗? 别那么快下结论! 它难道不能变成:

48 00 65 00 6C 00 6C 00 6F 00?

好吧……从技术上看,没错,我确实认为这行得通,而事实上那些先行者们确实希望能把Unicode码点存储为HE模式或LE模式,究竟哪种模式能让他们那颗特别的CPU运行得最快或最慢,究竟哪种模式更适用于早晨或晚上,这些都已经不重要了;重要的是,现在已经存在两种存储Unicode的方法。12 于是,人们被迫妥协于那个要求在所有Unicode字符串的前面先行存储FE FF这两个字节的诡异约定,这两个字节就是Unicode的字节顺序标记(BOM, Byte Order Mark);如果BOM的高位字节与低位字节互换,则形如FF FE,这时读取字符串的人就知道此后每两个字节都要做一次调换。 吁! 不过,桀骜不驯的那些Unicode字符串并非个个都会在前面乖乖加上BOM。

12. HE=high endian=大头在前,LE=low endian=小头在前,这源于鸡蛋先敲大头还是先敲小头的典故,下文解释了这两种的区别,即,HE模式下的字节序标记为FF FE,LE模式下的字节序标记为FE FF——译者草注

enter image description here

暂时看来,这够应付一阵子了,不过程序员们又开始抱怨了。 “看看那些零!”他们会这样念叨,因为他们是美国人,他们只看英文,他们那个小得可怜的字符集甚至不会用到大于U+00FF的任何码点。 还有一个原因,他们是自由散漫的加州嬉皮士,他们习惯于厉行节约(以及冷嘲热讽)。 如果他们是德州佬,才不在乎鲸吞牛饮两倍的字节容量呢。13 不过,那些加州怪胎可无法接受放任字符串存储空间翻番的主意,更何况现如今所有这些吊儿郎当的文档都已经使用了各种ANSI字符集及DBCS字符集,又该找谁来全盘转换它们呢? 难道去找新闻部14 仅仅因为这一个原因,大多数人都决定先搁置Unicode几年再说;与此同时,情况变得越来越糟。

13. 这涉及到美国的地域文化,加州=加利福尼亚州=特点是嬉皮挑战权威,德州=德克萨斯州=特点是地广人稀——译者草注
14. 这应该是作者调侃新闻部的一个梗——译者草注

于是,人们发明出UTF-8这样的天才概念。15 UTF-8是另外一套存储字符串Unicode码点信息的机制,它在内存中使用字长为8个比特的字节存储那些神奇的U+数字。 在UTF-8编码机制中,码值在0至127之间的所有码点均存储为一个字节。 只有存储码值大于或等于128的码点时,才会用到第二个字节、第三个字节、……事实上最多可使用六个字节。

15. UTF=unicode transform format, UCS=Universal Character Set,可能需说明Unicode与UCS的渊源——译者草注

enter image description here16

16. 细心的读者会发现,除第一行外,每一行中二进制字节串的可用码点数都要大于最大码点与最小码点的差值,这个差值恰好为最小码点所表示的数。码点对二进制串的转换算法简述为,任意码值均展开为区间内的最大码点的二进制位数,将这些二进制位依次填入第3列二进制字节串的v处即可。码值处于第1行时展开成7个二进制位,第二行展开成11个,第三行16个,第四行21个,第五行26个,第六行31个——译者草注
致编辑:这个表格为3列表格,列标题分别为“最小码点(十六进制)”、“最大码点(十六进制)”、“字节串(二进制)”

UTF-8编码机制具有净边际效应——UTF-8编码下的英文与ASCII编码下的英文看起来完全一样,因此美国人甚至感觉不到任何不对劲的地方。 只有剩下来的那些地球人不得不耍猴似的钻火圈。致编辑:这种措辞是为了体现作者调侃美国人优越意识时的戏谑 具体到“Hello”这个字符串,其Unicode码点为U+0048 U+0065 U+006C U+006C U+006F,使用UTF-8编码则存储为48 65 6C 6C 6F,而这(请注意!)恰好等同于该字符串使用这个星球上ASCII字符集、ANSI字符集、及各种OEM字符集等进行存储时的结果。致编辑:作者这时已经偷换了概念,这里的非Unicode字符集均默认为编码方式。需要加译者注吗? 现在,如果你冲动得要使用注音字符、或希腊字符、或克林贡字符,则请务必使用多个字节来存储一个码点——但美国人永远看不到这些字节。 (UTF-8还附送一个利好信息:那些老气横秋的字符串处理代码只想找一个码值为0的字节作为空位终止符,它们傲慢得不屑于一遇到不认识的字节就截断字符串。)
目前已经讲了三种编码Unicode的方式。 那些传统的双字节存储法可统称为UCS-2(2代表这种编码有两个字节)或UTF-16(16代表这种编码有16个比特);仍需厘清的是,这种UCS-2编码到底是HE模式、还是LE模式? 第三种是广受欢迎的新编码标准UTF-8,它具有坚守岗位的优良品行——不管是幸福邂逅纯英文字符,还是不期而遇那种两耳不闻窗外事、一心只读ASCII的死板程序,UTF-8都可以正常工作。
其实也存在着一些其他的Unicode编码方式。 有一种和UTF-8很像的编码叫做UTF-7,但它承诺高位始终是0,这样即便那种严厉刻板的认为7个比特就“足够了,谢谢”的极权主义邮件系统,也不得不让Unicode安全过境、毫发无伤。 还有一种UCS-4编码,这里每个码点都会存储为4个字节,它的好处在于所有码点在存储后的字节数都相同,而糟糕的是:天啦!即使蛮荒鲁钝的德州佬也不至于胆大妄为得浪费如此之多的内存。致编辑:给(德州佬)加了定语,待商榷
事实上,现在你思考事物的方式已蜕变成:那些柏拉图理想国中的字符都表示成了Unicode码点,而这些Unicode码点在编码时也可以使用任何一种因循守旧的编码机制!致编辑:(因循守旧)是为了不与下文重复,且流露出派系延承之意 举个例子,“Hello”的Unicode码点串为(U+0048 U+0065 U+006C U+006C U+006F),编码时可以使用ASCII编码、或老式希腊语编码(OEM代码页737)、或新式希伯来语编码(ANSI代码页1255)、或迄今所发明的上百种编码之一,但要记住一点17 其中某些字符可能不得而见! 对于试图呈现的Unicode码点,如果在尝试用来表示它的编码机制中找不到对应的字符,则通常会返回一个小小的问号:?致编辑:这个问号应为英文半角符号 或者,如果你人品够好,会得到一个小盒子:�。18 你得到了什么?是“?”还是“�”?

17. 简述ASCII编码、OEM编码、ANSI编码、Unicode编码的前世今生——译者草注
18. 有时?有时�的原因——译者草注

这个世界上有上百种传统编码机制,它们只能正确存储某些码点,并把所有其他码点都变成问号。 其中一些常用的英文编码如Windows-1252(Windows 9x平台上用于西欧语系的标准编码)和ISO-8859-1,后者又称作Latin-1(同样适用于任意的西欧语言)。 但如果尝试用这些编码存储俄语字母或希伯来语字母,则会得到一堆问号。 UTF-7、UTF-8、UTF-16及UTF-32都有着上好的品质,它们都能正确存储任意码点。

一个最重要的关于编码的真理

即使完全不记得刚刚讲过的任何东西,也请你记住一条最最重要的真理。 不知道其编码的字符串没有任何意义! 你再也不能把头埋在沙堆里,假装“纯”文本就是一些ASCII字符。

- 根本就没有“纯文本”这种东西!

假设有一个字符串,不管它是在内存中、在文件中还是在邮件消息中,都必须知道它的编码是什么,否则就不能正确地解读它,也不能正确地将其显示给用户。
几乎所有诸如“我的网站看上去全是乱码”、“她读不了我发的带注音符号的邮件”之类的愚蠢问题都可以归结于某个稚气可掬的程序员,他不了解这样一个简单的事实,即,如果不说明具体的字符串到底是使用UTF-8编码、或ASCII编码、或ISO 8859-1编码(即Latin 1编码)、或Windows 1252编码(西欧语系编码),就无法正确地显示它,甚至找不到这个字符串的结束位置。 我们有上百种编码,而对于码值大于127的那些码点,一子落错,满盘皆输。致编辑:(all bets are off)是个俚语,查到的意思是(an expression meaning a situation in which one factor alone can change or cancel out everything)
如何才能获得字符串的编码信息呢? 嗯,确实有一些标准的套路可循。 如果是邮件消息,应该在消息体头部有这样一个字符串:

Content-Type: text/plain; charset="UTF-8"

如果是网页,最初的策略是让网站服务器随网页本身返回一个类似于Content-Type的HTTP报文头——但这个报文头不在HTML代码之内,而是在发送网页之前作为众多应答报文头之一先行发送。
这会带来一些问题。 假设有一个大型网站服务器,上面有很多站点和数以百计的网页,这些网页来自于很多人,他们操持着很多不同的语言,所有这些人都使用了其Microsoft FrontPage付费拷贝在审时度势后自动生成的编码。 网站服务器自己其实并不知道撰写各个文件时所用的编码,因此它也就无法发送Content-Type报文头。
如果能把网页文件的Content-Type报文头直接放在HTML文件内,那就方便了——当然这要用到某种特殊的标签。 嗯,这肯定会让纯化论者抓狂……你怎么能在不知道HTML文件编码的情况下就读取它呢?! 幸运的是,对于码值在32至127之间的字符,几乎所有的通用编码都表现得一模一样,因此你可以一直往下读完这段HTML代码,而不用担心遇到什么奇趣横生的字符:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

不过,这里的<meta>标签要作为<head>代码段中的第一个标签,这是因为网页浏览器一看到这个标签,就会停止解析网页,并使用这里指定的编码从头重新解析整个网页。
如果网页浏览器在HTTP报文头及<meta>标签中都没有找到Content-Type,又该怎么办? Internet Explorer对此做了一件真的非常有意思的事情。 它试图体察上意,其猜度的理论根据是:不管使用哪种语言、哪种编码,对于各种语言的代表性编码而言,各种字节在代表性文本中的出现频率应该有迹可寻。 这是因为,各种使用8个比特的老式代码页习惯于将本国字母放在128至255之间的不同位置,又因为所有的人类语言关于其字母使用频率都有着各不相同的特征分布规律,所以这确实有可能获得成功。 这真的很怪异,但它碰巧猜对的几率好像确实很高,高到让那些从不知Content-Type报文头为何物的稚拙网页写手可以沾沾自喜地在自己的网页浏览器中欣赏自己做的网页,嗯,看来很不错哦……直到有一天,他们写的东西恰好没有服从其母语的字母使用频率分布规律,而Internet Explorer则认定他们写的是韩语并做了对应的显示……我想,这恰好证明了Postel关于“宽进窄出”的那个著名法则——实话实说——并不是一条好的工程原则。致编辑:暂时没有找到这个Postel法则的出处 不管怎样,对那个可怜的网站浏览者来说,明明是保加利亚语编写的网站却显示成了韩语(甚至是前言不搭后语的韩语),他该怎么办呢? 他使用【视图 | 编码】菜单,挨个试上一堆各不相同的编码(光是西欧语系就有数十种编码可用),直到能拨开云雾见月明。 这里已经假定他知道这么做——而大多数人恰恰并不知道这样做!

enter image description here

鄙人的公司曾经发布过一款网站管理软件CityDesk,我们决定在其最新版本的内部全部采用UCS-2(双字节)版本的Unicode,这个版本的Unicode正好也是Visual Basic、COM及Windows NT/2000/XP等使用的标配字符串类型。19 如果是C++代码,声明字符串时只要用wchar_t(wchar=wide char,意为“宽字符”)取代char即可;str系列函数也要替换为相应的wcs系列函数(例如,使用wcscatwcslen分别替换掉strcatstrlen)。 如果是C代码,要想建立一个UCS-2类型的文本字符串,只要在字符串前面加上一个“L”即可,形如:

19. 作者的广告时间及CityDesk简介——译者草注

L"Hello"

CityDesk发布网页时,会先把网页转换为UTF-8编码——后者多年来一直受到各种网页浏览器的良好支持。 鄙人的博客网站目前共有29种语言版本,它们全都是这样编码的;迄今为止,我还没有听到过有谁抱怨说看不了那些网页。20

20. 作者网站的多语言版本——译者草注

这篇文章已经写得够长了,而且我也不可能在这里涵盖字符编码和Unicode的方方面面,但我确实希望,既然你已经读到了这里,就应该储备了足够的知识以回归编程战场了——只是拜托别再用水蛭和咒语了,请使用抗生素这种新式武器吧。下面就看你的了!