本節書摘來自華章計算機《c語言程式設計魔法書:基于c11标準》一書中的第2章,第2.5節,作者 陳轶,更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。
我們從2.2節到2.4節講述的都是數值資訊(整數與浮點數),本小節我們将讨論字元資訊。在計算機中我們所處理的字元資訊,即文本資訊(包括數字、字母、文字、标點符号等)是以一種特定編碼格式來定義的。為了使世界各國的文本資訊能夠通用,就需要對字元編碼做标準化。我們現在最常用也最基本的字元編碼系統是ascii碼(american standard code for information interchange,美國資訊交換标準碼)。ascii碼定義每個字元僅占一個位元組,可表示阿拉伯數字0~9、26個大小寫英文字母,以及我們現在在标準鍵盤上能看到的所有标點符号、一些控制字元(比如換行、回車、換頁、振鈴等)。ascii碼最高位是奇偶校驗位,用于通信校驗,是以真正有編碼意義的是低7個比特,是以隻能用于表示128個字元(值從0~127)。由于ascii是美國國家标準,是以後來國際化标準組織将它進行國際标準化,定義為了iso/iec 646标準。兩者所定義的内容是等價的。
iso/iec 646對于英文系國家而言是基本夠用了,但是對于拉丁語系、希臘等國家來說就不夠用了。是以後來iso組織就把原先iso/iec 646所定義字元的最高位也用上了,這樣就又能增加128個不同的字元,釋出了iso/iec 8859标準。然而,歐洲大陸雖小,但國家卻有數百個,128種擴充字元仍然不夠用。是以後來就在8859的基礎上,引入了8859-n,n從1~16,每一種都支援了一定數量的不同的字母,這樣基本能滿足歐美國家的文字表示需求。當然,有些國家之間仍然需要切換編碼格式,比如iso/iec8859-1的語言環境看8859-2的就可能顯示亂碼,是以,還得切換到8859-2的字元編碼格式下才能正常顯示。
而在中國大陸,我們自己也定義了一套用于顯示簡體中文的字元集——gb2312。它在1981年5月1日開始實施,是中國國家标準的簡體中文字元集,全稱為《資訊交換用漢字編碼字元集·基本集》。它收錄了6763個漢字,包括拉丁字母、希臘字母、日語假名、俄語和蒙古語用的西裡爾字母在内的682個全角字元。然後又出現了gbk字元集,gbk1.0收錄了21886個符号,其中漢字就包含了21003個。gbk字元集主要擴充了繁體中文字。由于像gb2312與gbk能表示成千上萬種字元,是以這已經遠超1個位元組所能表示的範圍。它們所采用的是動态變長位元組編碼,并且與ascii碼相容。如果表示ascii碼部分,那麼僅1個位元組即可,并且該位元組最高位為0。如果要表示漢字等擴充字元,那麼頭1個位元組的最高位為1,然後再增加一個位元組(即用兩個位元組)進行表示。是以,理論上,除了第1個位元組的最高位不能動之外,其餘比特都能表示具體的字元資訊,因而最多可表示27+215=32896種字元。
當然,正由于gb2312與gbk主要用于亞洲國家,是以當歐美國家的人看到這些字元資訊時顯示的是亂碼,他們必須切換到相應的漢字編碼環境下看才能看到正确的文本資訊。為了能真正将全球各國語言進行互換通信,出現了unicode(universal character set,ucs)标準。它對應于編碼标準iso/iec 10646。unicode前後也出現了多個版本。早先的ucs-2采用固定的雙位元組編碼方式,理論上可表示216=65536種字元,是以極大地涵蓋了各種語言的文字元号。
不過後來,标準委員會意識到,對于像希伯來字母、拉丁字母等壓根就不需要用兩個位元組表示,而且定長的雙位元組表示與原有的ascii碼又不相容,是以後來出現了現在用得更多的utf-8編碼标準。utf-8屬于變長的編碼方式,它最少可用1個位元組表示1個字元,最多用4個位元組表示1個字元,判别依據就是看第1個位元組的最高位有多少個1。如果第1個位元組的最高位是0,那麼該字元用1個位元組表示;最高3位是110,那麼用2個位元組表示;最高4位是1110,那麼用3個位元組表示;最高位是11110,那麼該字元由4個位元組來表示。是以utf-8現在大量用于網絡通信的字元編碼格式,包括大多數網頁用的預設字元編碼也都是utf-8編碼。盡管utf-8更為靈活,而且也與ascii碼完全相容,但不利于程式解析。是以現在很多程式設計語言的編譯器以及運作時庫用得更多的是utf-16編碼來處理源代碼解析以及各類文本解析,它與之前的ucs-2編碼完全相容,但也是變長編碼方式,可用雙位元組或四位元組來表示一個字元。如果用雙位元組表示utf-16編碼的話,範圍從0x0000到0xd7ff,以及從0xe000到0xffff。這裡留出0xd800到0xdfff,不作為具體字元的編碼表示,而是用于四位元組編碼時的編碼替換。當utf-16表示0x10000到0x10ffff之間的字元時,先将該範圍内的值減去0x10000,使得結果落在0x00000到0xfffff範圍内。然後将結果劃分為高10位與低10位兩組。将低10位的值與0xdc00相加,獲得低16位;高10位與0xd800相加,獲得高16位。比如,一個unicode定義的碼點(code point)為0x10437的字元,用utf-16編碼表示的步驟如下。
1)先将它減去0x10000——0x10437-0x10000=0x0437。
2)将該結果分為低10位與高10位,0x0437用20位二進制表示為0000 0000 0100 0011 0111,是以高10位是0000 0000 01=0x01;低10位則是00 0011 0111,即0x037。
3)将高10位與0xd800相加,得到0xd801;将低10位與0xdc00相加,獲得0xdc37。是以最終utf-16編碼為0xd801dc37。
我們看到,盡管utf-16也是變長編碼表示,但是僅低16位就能表示很多字元符号,況且即便要表示更廣範圍的字元,也隻是第二種四位元組的表示方法,這遠比utf-8四種不同的編碼方式要簡潔很多。是以,utf-16用在很多程式設計語言運作時系統字元編碼的場合比較多。像現在的java、objective-c等程式設計語言環境内部系統所表示的字元都是utf-16編碼方式。
另外,現在還有utf-32編碼方式,這一開始也是unicode标準搞出來的ucs-4标準,它與ucs-2一樣,是定長編碼方式,但每個字元用固定的4位元組來表示。不過現在此格式用得很少,而且html5标準組織也公開聲明開發者應當盡量避免在頁面中使用utf-32編碼格式,因為在html5規範中所描述的編碼偵測算法,故意不對它與utf-16編碼做區分。