天天看點

關于編碼(轉載)

轉自:http://blog.sina.com.cn/s/blog_4b4409c30100vw9t.html

最初的unicode編碼是固定長度的,16位,也就是2兩個位元組代表一個字元,這樣一共可以表示65536個字元。顯然,這樣要表示各種語言中所有的字元是遠遠不夠的。Unicode4.0規範考慮到了這種情況,定義了一組附加字元編碼,附加字元編碼采用2個16位來表示,這樣最多可以定義1048576個附加字元,目前unicode4.0隻定義了45960個附加字元。

Unicode隻是一個編碼規範,目前實際實作的unicode編碼隻要有三種:UTF-8,UCS-2和UTF-16,三種unicode字元集之間可以按照規範進行轉換。

UTF-8

UTF-8是一種8位的unicode字元集,編碼長度是可變的,并且是ASCII字元集的嚴格超集,也就是說ASCII中每個字元的編碼在UTF-8中是完全一樣的。UTF-8字元集中,一個字元可能是1個位元組,2個位元組,3個位元組或者4個位元組長。一般來說,歐洲的字母字元長度為1到2個位元組,而亞洲的大部分字元則是3個位元組,附加字元為4個位元組長。

Unix平台中普遍支援UTF-8字元集,HTML和大多數浏覽器也支援UTF-8,而window和java則支援UCS-2。

UTF-8的主要優點:

  • 對于歐洲字母字元需要較少的存儲空間。
  • 容易從ASCII字元集向UTF-8遷移。

UCS-2

UCS-2是固定長度為16位的unicode字元集。每個字元都是2個位元組,UCS-2隻支援unicode3.0,是以不支援附加字元。

UCS-2的優點:

  • 對于亞洲字元的存儲空間需求比UTF-8少,因為每個字元都是2個位元組。
  • 處理字元的速度比UTF-8更快,因為是固定長度編碼的。
  • 對于windows和java的支援更好。

UTF-16

UTF-16也是一種16位編碼的字元集。實際上,UTF-16就是UCS-2加上附加字元的支援,也就是符合unicode4.0規範的UCS-2。是以UTF-16是UCS-2的嚴格超集。

UTF-16中的字元,要麼是2個位元組,要麼是4個位元組表示的。UTF-16主要在windows2000以上版本使用。

UTF-16相對UTF-8的優點,和UCS-2是一緻的。

Oracle從7.0開始提供對Unicode的支援。Oracle個版本的unicode字元集支主要有:

AL32UTF8

一種UTF-8編碼的字元集,支援最新的unicode4.0标準。字元長度為1,2或者3個位元組,附加字元則為4位元組長。

UTF8

支援unicode3.0的UTF-8編碼方式。由于附加字元是在unicode3.1中提出的,UTF8不支援附加字元。但是unicode3.0已經為附加字元預留了編碼空間,是以即使在UTF8的資料庫中插入附加字元,也是可以的,隻是資料庫會将該字元分隔成兩部分,需要占6個字元的長度。是以,如果需要支援附加字元,那麼建議将資料庫的字元集切換為新的AL32UTF8。

UTF8可用于資料庫字元集,也可用于國家字元集。

UTFE

UTFE是基于EBCDIC平台的unicode字元集,就像ASCII平台上的UTF8一樣。不同的是,UTFE中,每個字元可能占1,2,3或者4個位元組,而附加字元則需要2個4個位元組,也就是8個位元組來表示。

AL16UTF16

AL16UTF16是一種UTF-16編碼的unicode字元集,在Oracle中用于國家字元集。

AL24UTFFSS

該字元集隻支援unicode1.1規範,在Oracle7.2~8i版本中使用,目前已經淘汰。

CString在Unicode下一個位元組占16bit,在ascii下占8bit,改成char數組後在什麼環境下都一樣的

編寫程式最好是:同一個源檔案既可以在UNICODE下編譯,又可以在ANSI下編譯

工程--設定--C/C++--預處理器,可以定義辨別符,如UNICODE,_UNICODE,辨別是按ASCII編譯,還是按UNICODE編譯

#include <tchar.h>

char定義全部 改成TCHAR,TCHAR根據設定不同定義為char或者wchar

字元串加用TEXT宏,如TEXT("你好"),根據編譯器的設定不同,分别定義為ANSI或者UNICODE版本

字元串也大部分有其通用版本:

最大長度版比标準版多一個參數,表示緩沖區的長度

有v的其參數為參數清單指針,使用va_list、va_start和va_end宏

C提供的字元串函數: ASCII 寬字元 通用形式

1.可變參數:

标準版 sprintf swprintf _stprintf

最大長度版 _snprintf _snwprintf _sntprintf

WindowsNT版 wsprintfA wsprintfW wsprintf

2.數組的指針作參數:

标準版 vsprintf vswprintf _vstprintf

最大長度版 _vsnprintf _vsnwprintf _vsntprintf

WindowsNT版 wvsprintfA wvsprintfW wvsprintf

以下引用《Windows程式設計》

美國标準

早期計算機的字元碼是從Hollerith卡片(号稱不能被折疊、卷曲或毀傷)發展而來的,該卡片由Herman Hollerith發明并首次在1890年的美國人口普查中使用。6位字元碼系統BCDIC(Binary-Coded Decimal Interchange Code:二進制編碼十進制交換編碼)源自Hollerith代碼,在60年代逐漸擴充為8位EBCDIC,并一直是IBM大型主機的标準,但沒使用在其它地方。

美國資訊交換标準碼(ASCII:American Standard Code for Information Interchange)起始于50年代後期,最後完成于1967年。開發ASCII的過程中,在字元長度是6位、7位還是8位的問題上産生了很大的争議。從可靠性的觀點來看不應使用替換字元,是以ASCII不能是6位編碼,但由于費用的原因也排除了8位版本的方案(當時每位的儲存空間成本仍很昂貴)。這樣,最終的字元碼就有26個小寫字母、26個大寫字母、10個數字、32個符号、33個句柄和一個空格,總共128個字元碼。ASCII現在記錄在ANSI X3.4-1986字元集-用于資訊交換的7位美國國家标準碼(7-Bit ASCII:7-Bit American National Standard Code for Information Interchange),由美國國家标準協會(American National Standards Institute)釋出。圖2-1中所示的ASCII字元碼與ANSI檔案中的格式相似。

ASCII有許多優點。例如,26個字母代碼是連續的(在EBCDIC代碼中就不是這樣的);大寫字母和小寫字母可通過改變一位資料而互相轉化;10個數字的代碼可從數值本身友善地得到(在BCDIC代碼中,字元「0」的編碼在字元「9」的後面!)

最棒的是,ASCII是一個非常可靠的标準。在鍵盤、視訊顯示卡、系統硬體、列印機、字型檔案、作業系統和Internet上,其它标準都不如ASCII碼流行而且根深蒂固。

 

圖2-1 ASCII字元集

國際方面

ASCII的最大問題就是該縮寫的第一個字母。ASCII是一個真正的美國标準,是以它不能良好滿足其它講英語國家的需要。例如英國的英鎊符号(£)在哪裡?

英語使用拉丁(或羅馬)字母表。在使用拉丁語字母表的書寫語言中,英語中的單詞通常很少需要重音符号(或讀音符号)。即使那些傳統慣例加上讀音符号也無不當的英語單字,例如c鰋perate或者résumé,拼寫中沒有讀音符号也會被完全接受。

但在美國以南、以北,以及大西洋地區的許多國家,在語言中使用讀音符号很普遍。這些重音符号最初是為使拉丁字母表适合這些語言讀音不同的需要。在遠東或西歐的南部旅遊,您會遇到根本不使用拉丁字母的語言,例如希臘語、希伯來語、阿拉伯語和俄語(使用斯拉夫字母表)。如果您向東走得更遠,就會發現中國象形漢字,日本和北韓也采用漢字系統。

ASCII的曆史開始于1967年,此後它主要緻力于克服其自身限制以更适合于非美國英語的其它語言。例如,1967年,國際标準化組織(ISO:International Standards Organization)推薦一個ASCII的變種,代碼0x40、0x5B、0x5C、0x5D、0x7B、0x7C和0x7D「為國家使用保留」,而代碼0x5E、0x60和0x7E标為「當國内要求的特殊字元需要8、9或10個空間位置時,可用于其它圖形符号」。這顯然不是一個最佳的國際解決方案,因為這并不能保證一緻性。但這卻顯示了人們如何想盡辦法為不同的語言來編碼的。

擴充ASCII

在小型計算機開發的初期,就已經嚴格地建立了8位位元組。是以,如果使用一個位元組來儲存字元,則需要128個附加的字元來補充ASCII。1981年,當最初的IBM PC推出時,視訊卡的ROM中燒有一個提供256個字元的字元集,這也成為IBM标準的一個重要組成部分。

最初的IBM擴充字元集包括某些帶重音的字元和一個小寫希臘字母表(在數學符号中非常有用),還包括一些塊型和線狀圖形字元。附加的字元也被添加到ASCII控制字元的編碼位置,這是因為大多數控制字元都不是拿來顯示用的。

該IBM擴充字元集被燒進無數顯示卡和列印機的ROM中,并被許多應用程式用于修飾其文字模式的顯示方式。不過,該字元集并沒有為所有使用拉丁字母表的西歐語言提供足夠多的帶重音字元,而且也不适用于Windows。Windows不需要圖形字元,因為它有一個完全圖形化的系統。

在Windows 1.0(1985年11月發行)中,Microsoft沒有完全放棄IBM擴充字元集,但它已退居第二重要位置。因為遵循了ANSI草案和ISO标準,純Windows字元集被稱作「ANSI字元集」。ANSI草案和ISO标準最終成為ANSI/ISO 8859-1-1987,即「American National Standard for Information Processing-8-Bit Single-Byte Coded Graphic Character Sets-Part 1: Latin Alphabet No 1」,通常也簡寫為「Latin 1」。

在Windows 1.0的《Programmer's Reference》中印出了ANSI字元集的最初版本,如圖2-2所示。

圖2-2 Windows ANSI字元集(基于ANSI/ISO 8859-1)

空方框表示該位置未定義字元。這與ANSI/ISO 8859-1的最終定義一緻。ANSI/ISO 8859-1僅顯示了圖形字元,而沒有控制字元,是以沒有定義DEL。此外,代碼0xA0定義為一個非斷開的空格(這意味着在編排格式時,該字元不用于斷開一行),代碼0xAD是一個軟連字元(表示除非在行尾斷開單詞時使用,否則不顯示)。此外,ANSI/ISO 8859-1将代碼0xD7定義為乘号(*),0xF7為除号(/)。Windows中的某些字型也定義了從0x80到0x9F的某些字元,但這些不是ANSI/ISO 8859-1标準的一部分。

MS-DOS 3.3(1987年4月發行)向IBM PC使用者引進了代碼頁(code page)的概念,Windows也使用此概念。代碼頁定義了字元的映像代碼。最初的IBM字元集被稱作代碼頁437,或者「MS-DOS Latin US)。代碼頁850就是「MS-DOS Latin 1」,它用附加的帶重音字母(但不是圖2-2所示的Latin 1 ISO/ANSI标準)代替了一些線形字元。其它代碼頁被其它語言定義。最低的128個代碼總是相同的;較高的128個代碼取決于定義代碼頁的語言。

在MS-DOS中,如果使用者為PC的鍵盤、顯示卡和列印機指定了一個代碼頁,然後在PC上建立、編輯和列印檔案,一切都很正常,每件事都會保持一緻。然而,如果使用者試圖與使用不同代碼頁的使用者交換檔案,或者在機器上改變代碼頁,就會産生問題。字元碼與錯誤的字元相關聯。應用程式能夠将代碼頁資訊與檔案一起儲存來試圖減少問題的産生,但該政策包括了某些在代碼頁間轉換的工作。

雖然代碼頁最初僅提供了不包括帶重音符号字母的附加拉丁字元集,但最終代碼頁的較高的128個字元還是包括了完整的非拉丁字母,例如希伯來語、希臘語和斯拉夫語。自然,如此多樣會導緻代碼頁變得混亂;如果少數帶重音的字母未正确顯示,那麼整個文字便會混亂不堪而不可閱讀。

代碼頁的擴充正是基于所有這些原因,但是還不夠。斯拉夫語的MS-DOS代碼頁855與斯拉夫語的Windows代碼頁1251以及斯拉夫語的Macintosh代碼頁10007不同。每個環境下的代碼頁都是對該環境所作的标準字元集修正。IBM OS/2也支援多種EBCDIC代碼頁。

但等一下,你會發現事情變得更糟糕。

雙位元組字元集

迄今為止,我們已經看到了256個字元的字元集。但中國、日本和南韓的象形文字元号有大約21,000個。如何容納這些語言而仍保持和ASCII的某種相容性呢?

解決方案(如果這個說法正确的話)是雙位元組字元集(DBCS:double-byte character set)。DBCS從256代碼開始,就像ASCII一樣。與任何行為良好的代碼頁一樣,最初的128個代碼是ASCII。然而,較高的128個代碼中的某些總是跟随着第二個位元組。這兩個位元組一起(稱作首位元組和跟随位元組)定義一個字元,通常是一個複雜的象形文字。

雖然中文、日文和韓文共享一些相同的象形文字,但顯然這三種語言是不同的,而且經常是同一個象形文字在三種不同的語言中代表三件不同的事。Windows支援四個不同的雙位元組字元集:代碼頁932(日文)、936(簡體中文)、949(韓語)和950(繁體漢字)。隻有為這些國家(地區)生産的Windows版本才支援DBCS。

雙字元集問題并不是說字元由兩個位元組代表。問題在于一些字元(特别是ASCII字元)由1個位元組表示。這會引起附加的程式設計問題。例如,字元串中的字元數不能由字元串的位元組數決定。必須剖析字元串來決定其長度,而且必須檢查每個位元組以确定它是否為雙位元組字元的首位元組。如果有一個指向DBCS字元串中間的指針,那麼該字元串前一個字元的位址是什麼呢?慣用的解決方案是從開始的指針分析該字元串!

Unicode解決方案

我們面臨的基本問題是世界上的書寫語言不能簡單地用256個8位代碼表示。以前的解決方案包括代碼頁和DBCS已被證明是不能滿足需要的,而且也是笨拙的。那什麼才是真正的解決方案呢?

身為程式寫作者,我們經曆過這類問題。如果事情太多,用8位數值已經不能表示,那麼我們就試更寬的值,例如16位值。而且這很有趣的,正是Unicode被制定的原因。與混亂的256個字元代碼映像,以及含有一些1位元組代碼和一些2位元組代碼的雙位元組字元集不同,Unicode是統一的16位系統,這樣就允許表示65,536個字元。這對表示所有字元及世界上使用象形文字的語言,包括一系列的數學、符号和貨币機關符号的集合來說是充裕的。

明白Unicode和DBCS之間的差別很重要。Unicode使用(特别在C程式設計語言環境裡)「寬字元集」。「Unicode中的每個字元都是16位寬而不是8位寬。」在Unicode中,沒有單單使用8位數值的意義存在。相比之下,在雙位元組字元集中我們仍然處理8位數值。有些位元組自身定義字元,而某些位元組則顯示需要和另一個位元組共同定義一個字元。

處理DBCS字元串非常雜亂,但是處理Unicode文字則像處理有秩序的文字。您也許會高興地知道前128個Unicode字元(16位代碼從0x0000到0x007F)就是ASCII字元,而接下來的128個Unicode字元(代碼從0x0080到0x00FF)是ISO 8859-1對ASCII的擴充。Unicode中不同部分的字元都同樣基于現有的标準。這是為了便于轉換。希臘字母表使用從0x0370到0x03FF的代碼,斯拉夫語使用從0x0400到0x04FF的代碼,美國使用從0x0530到0x058F的代碼,希伯來語使用從0x0590到0x05FF的代碼。中國、日本和南韓的象形文字(總稱為CJK)占用了從0x3000到0x9FFF的代碼。

Unicode的最大好處是這裡隻有一個字元集,沒有一點含糊。Unicode實際上是個人計算機行業中幾乎每個重要公司共同合作的結果,并且它與ISO 10646-1标準中的代碼是一一對應的。Unicode的重要參考文獻是《The Unicode Standard,Version 2.0》(Addison-Wesley出版社,1996年)。這是一本特别的書,它以其它檔案少有的方式顯示了世界上書寫語言的豐富性和多樣性。此外,該書還提供了開發Unicode的基本原理和細節。

Unicode有缺點嗎?當然有。Unicode字元串占用的記憶體是ASCII字元串的兩倍。(然而壓縮檔案有助于極大地減少檔案所占的磁盤空間。)但也許最糟的缺點是:人們相對來說還不習慣使用Unicode。身為程式寫作者,這就是我們的工作。

寬字元和 C

對C程式寫作者來說,16位字元的想法的确讓人掃興。一個char和一個位元組同寬是最不能确定的事情之一。沒幾個程式寫作者清楚ANSI/ISO 9899-1990,這是「美國國家标準程式設計語言-C」(也稱作「ANSI C」)通過一個稱作「寬字元」的概念來支援用多個位元組代表一字元的字元集。這些寬字元與常用的字元完美地共存。

ANSI C也支援多位元組字元集,例如中文、日文和韓文版本Windows支援的字元集。然而,這些多位元組字元集被當成單位元組構成的字元串看待,隻不過其中一些字元改變了後續字元的含義而已。多位元組字元集主要影響C語言程式執行時期連結庫函數。相比之下,寬字元比正常字元寬,而且會引起一些編譯問題。

寬字元不需要是Unicode。Unicode是一種可能的寬字元集。然而,因為本書的焦點是Windows而不是C執行的理論,是以我将把寬字元和Unicode作為同義語。

Char資料型态

假定我們都非常熟悉在C程式中使用char資料型态來定義和儲存字元跟字元串。但為了便于了解C如何處理寬字元,讓我們先回顧一下可能在Win32程式中出現的标準字元定義。

下面的語句定義并初始化了一個隻包含一個字元的變量:

char c = 'A' ;

變量c需要1個位元組來儲存,并将用十六進制數0x41初始化,這是字母A的ASCII代碼。

您可以像這樣定義一個指向字元串的指針:

char * p ;

因為Windows是一個32位作業系統,是以指針變量p需要用4個位元組儲存。您還可初始化一個指向字元串的指針:

char * p = "Hello!" ;

像前面一樣,變量p也需要用4個位元組儲存。該字元串儲存在靜态記憶體中并占用7個位元組-6個位元組儲存字元串,另1個位元組儲存終止符号0。

您還可以像這樣定義字元數組:

char a[10] ;

在這種情況下,編譯器為該數組保留了10個位元組的儲存空間。表達式sizeof(a)将傳回10。如果數組是整體變量(即在所有函數外定義),您可使用像下面的語句來初始化一個字元數組:

char a[] = "Hello!" ;

如果您将該數組定義為一個函數的區域變量,則必須将它定義為一個static變量,如下:

static char a[] = "Hello!" ;

無論哪種情況,字元串都儲存在靜态程式記憶體中,并在末尾添加0,這樣就需要7個位元組的儲存空間。

寬字元

Unicode或者寬字元都沒有改變char資料型态在C中的含義。char繼續表示1個位元組的儲存空間,sizeof (char)繼續傳回1。理論上,C中1個位元組可比8位長,但對我們大多數人來說,1個位元組(也就是1個char)是8位寬。

C中的寬字元基于wchar_t資料型态,它在幾個表頭檔案包括WCHAR.H中都有定義,像這樣:

typedef unsigned short wchar_t ;

是以,wchar_t資料型态與無符号短整數型态相同,都是16位寬。

要定義包含一個寬字元的變量,可使用下面的語句:

wchar_t c = 'A' ;

變量c是一個雙位元組值0x0041,是Unicode表示的字母A。(然而,因為Intel微處理器從最小的位元組開始儲存多位元組數值,該位元組實際上是以0x41、0x00的順序儲存在記憶體中。如果檢查Unicode文字的計算機儲存應注意這一點。)

您還可定義指向寬字元串的指針:

wchar_t * p = L"Hello!" ;

注意緊接在第一個引号前面的大寫字母L(代表「long」)。這将告訴編譯器該字元串按寬字元儲存-即每個字元占用2個位元組。通常,指針變量p要占用4個位元組,而字元串變量需要14個位元組-每個字元需要2個位元組,末尾的0還需要2個位元組。

同樣,您還可以用下面的語句定義寬字元數組:

static wchar_t a[] = L"Hello!" ;

該字元串也需要14個位元組的儲存空間,sizeof (a) 将傳回14。索引數組a可得到單獨的字元。a[1] 的值是寬字元「e」,或者0x0065。

雖然看上去更像一個印刷符号,但第一個引号前面的L非常重要,并且在兩個符号之間必須沒有空格。隻有帶有L,編譯器才知道您需要将字元串存為每個字元2位元組。稍後,當我們看到使用寬字元串而不是變量定義時,您還會遇到第一個引号前面的L。幸運的是,如果忘記了包含L,C編譯器通常會給提出警告或錯誤資訊。

您還可在單個字元文字前面使用L字首,來表示它們應解釋為寬字元。如下所示:

wchar_t c = L'A' ;

但通常這是不必要的,C編譯器會對該字元進行擴充,使它成為寬字元。