程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 轉載:從程序員的角度看ASCII, GB2312, UNICODE, UTF-8,asciiutf-8

轉載:從程序員的角度看ASCII, GB2312, UNICODE, UTF-8,asciiutf-8

編輯:C++入門知識

轉載:從程序員的角度看ASCII, GB2312, UNICODE, UTF-8,asciiutf-8


以下內容轉自博客:http://blog.chinaunix.net/uid-22670933-id-1771613.html。

一、字符編碼是怎麼回事

0. 概念

字節是計算機的最基本存儲單位,一個字節包括8個位.

字符是一種文字的基本單位,比如'A' 是一個字符,'漢' 也是一個字符.

 

1. 計算機被發明之後,程序員們編寫了很多復雜的計算讓計算機運行.

但是一個問題是,計算機如何把辛苦計算的結果告知程序員? 假設計算機把計算結果放在某個寄存器,內容是 1010010

總不能讓程序員去檢測每個引腳的電位吧? 還是得有個顯示器.

 

顯示器是依靠點陣來顯示圖像的. CPU必須告訴顯示器,當CPU 把一個字節的數據比如 00101010 放入顯示器寄存器的時候,顯示器要顯示怎樣的一個點陣(圖像),這個圖像就是我們人類可以看得懂的字符. 這樣問題就解決了,比如當CPU把00110001放入顯示器寄存器時,顯示器就顯示(控制點陣畫出一個圖像-字體)字符 "1", 這是一個查表的過程,內存中的值(內碼)和字符是一一對應的. 問題是這個對應關系是可以自由確定的,我可以指定顯示器把 00110001(內碼) 顯示為字符 "1",也可以指定顯示為字符"2". 這樣當然會引起混亂,同一個內碼被映射為不同的字符,不利於人們的交流.

 

美國國家標准學會(ANSI)決定著手解決這個問題, 英語有一個很小的字符集,26個字母再加上一些控制字符和標點符號,7位2進制值就足以表示所有的變化.於是ANSI公布了一個標准的對應關系,以字節為單位. 當內碼為 0110001 時,大家都公認它代表字符 "1",在所有顯示器都顯示為同一個字符. 這樣大家就可以按照同一個標准相互交換數據而不會引起誤解. 這個表就是一個包含了128項的對應關系, 叫做 "ASCII", 美國信息交換標准代碼.

2.對於中國這樣不使用ABC字符的國家來說,如何顯示自己的文字是一個大問題.

 

我們可以制定一個內碼表,指定一個內碼對應一個漢字. (由於中文的字符非常多,所以一個字節是不夠的,至少也要有2個字節存儲一個內碼.) 這是很容易的,只要國家公布一個標准的內碼字符對應表,大家都遵照這個表就可以了.但是還是有一些問題要注意:

(1). 即使在中國,計算機還是得能顯示英文吧?

而英文的內碼已經有 ASCII 標准在先,並且已經有無數的程序已經在這個標准上運行了很多年,成為不可或缺的部分. 所以我們新制定的內碼表必須和 ASCII 兼容.

(2) 很多C語言的庫函數是以內碼0作為字符串結束標志的,為了兼容那些以前就已經編寫好,並且運行良好的程序,我們指定的內碼中不能含有值為0的字節.

巧合的是,所有的ASCII內碼的最高位都為0. 那麼我們只要讓第一個和第二個字節(一個漢字占用2個字節)的最高位都為1,這樣既和ASCII內碼區分開來,又不會出現0.符合這個規則的內碼(2字節長)理論上一共可以標識 127 * 127 = 16129個字符(實際上只用了6000多個碼位,保留了一些,不過也已經夠用了,常用的中文字符只有4000多個).

我們國家公布的這個內碼標准表就是GB2312. 

原有的英文軟件可以很好的運行,C的庫函數也不用做修改, 比如 strlen("ABC") 在GB2312表示的內碼中, 由於GB2312對英文字符的編碼是和ASCII完全一樣的,所以返回

3.對於 strlen("A漢字"), 由於strlen()是以內碼為0作為邊界的,而所有中文字符的GB2312內碼高位都為1,不會出現0,並且每個漢字占用2個字節,所以 strlen 返回5. 對於程序來說只要檢查一個字節的最高位,就可以很容易的判斷這個字符是中文還是英文字符,非常方便.

 

"一個字母一個字節,一個漢字2個字節" 的觀念深入人心.

有了GB2312之後,漢字顯示/存儲/交換就基本上沒什麼問題了.

幾乎所有的非英語國家都制定了和GB2312類似兼容ASCII的內碼字符對應表.

(BIG5 由於有幾個字符的內碼和ASCII相同但表示不同的字符,不符合2.(1)條件.所以被認為是有"瑕疵"的.) 

 

3. 很明顯,GB2312的碼位是不夠的, 一個例子就是有很多人的人名電腦裡打不出來.(只有6000多個碼位,而<<康熙字典>>就收錄了4萬多個漢字).所以後來有出現了諸如GBK, GB18030以及同期流行於台灣香港的BIG5編碼. 雖然編碼有些不同, 但是設計思想是一致的: 兼容ASCII,並確保不會有某個字節值為0的內碼出現.有一個共同的特點是: 它們都是局部的標准,只流行於某個地區/國家內.

 

4.由於內碼表都是各個國家獨自制定的,同一個內碼,在不同的國家表示的可能是不同的字符.(除了ASCII字符, ASCII字符在所有國家指定的內碼表中都有同樣的值.)不利於國家間的信息交換. 於是 UNICODE 應運而生.

UNICODE 采用一種很簡單的辦法來解決這個問題. 就是采用2個 - UCS-2 (或者4個字節 - UCS-4)字節標識一個字符. 2個字節總共可以表示65535個字符,足夠表示世界上的所有語言的所有字符.(漢字不就有4萬多個嗎,65535怎麼夠. 我估計只是常用的漢字幾千個被編在UCS-2中吧. 目前被正式編碼到UNICODE碼位的只有不超過65534個, 所以就目前的情況來說,用2個字節是可以的.) 注意 UCS-4, UCS-2 和 ASCII是向下兼容的,只要前面補0就可以了.這點很重要,可以一直擴展下去包含全宇宙的字符.

現在地球上每個字符在所有采用UNICODE字符編碼的計算機內都有一個唯一的內碼了.

要注意, 除了ASCII字符外,其他國家文字的字符的內碼是重新分配過的,不一定和各國原有的編碼相同.比如大部分漢字的GB2312內碼和UNICODE 內碼都是不同的.

 

5. 很顯然,對於英語國家來說,UNICODE內碼非常浪費空間,對於UCS-2 浪費了50%的存儲空間,對於 UCS-4 則浪費了70%的存儲空間. 而且還有一個更大的問題, UNICODE的內碼中含有很多 '\0', 原有的C標准庫函數沒辦法處理這些字符串.於是有人發明了一種針對UNICODE的變換規則,把UNICODE字符串中的0去除. 注意這個變換規則不是通過查表實現的,而只要用一些位移操作就可以實現. 這就是UTF8.

 

總結:

UTF8 只是 UNICODE內碼在存儲/傳輸時的狀態. 而從GB2312編碼轉換到UNICODE編碼需要查表. UTF8 和 UNICODE 的關系 與 GB2312 和 UNICODE的關系有本質的不同. UTF8 和 UNICODE 是一個人的兩個面孔, GB2312 和 UNICODE 是兩個人.

所以,要實現UTF8編碼到GB2312編碼的轉換必須先把 UTF8編碼還原為UNICODE編碼,再通過查表的方式,把UNICODE編碼轉化為GB2312編碼.

以上,雖然說得不是很嚴謹(比如GB2312其實是區位碼,真正的內碼還要給每個字節加上A0, 這些我都沒提,免得分散注意力),但是文字編碼的原理大致就是那麼回事,理解就好了. 要想詳細了解細節Google一下能找到很多資料.

 

二、字符編碼的編程相關問題

1. Windows從NT開始,內核使用UNICODE內碼. 為了向前兼容,前端使用的還是GB2312內碼(中文環境). 

所以用 Visual Studio 編寫代碼時, 如果在CPP文件中寫這樣一句 const char* pszText = "中文", 編譯器讓 pszText 指向"中文"的GB2312內碼值的內存空間. 當調用 printf(pszText)時, WINAPI 把這個GB2312字符串轉化為UNICODE字符串再輸出.(WIndows自然知道你的編碼是GB2312,因為你在Windows系統中設置的語言區域是中國, CodePage 936. 如果改成其它語言,就會顯示為亂碼.)

微軟非常鼓勵Windows程序員用Unicode編寫程序,很明顯,由於Windows內核就是原生的Unicode環境,調用API時,省卻了編碼轉換的操作,效率更高. 而且一個額外的好處就是不會有亂碼. 注意,MS的C/C++編譯器把sizeof(wchar_t)設置為2個字節. 由於目前所有的UNICODE字符只有65534個碼位(BMP),所以用2個字節是沒問題的.

 

2. Linux系統(比如Ubuntu)現在一般都用UTF8編碼了.

我們在Linux下創建CPP文件並添加同樣的: const char* pszText = "中文" 編譯器會讓 pszText 指向"中文"UTF8的內碼值的內存空間.Linux的終端可以理解為一個只接收UTF8字符串的顯示器. 任何被寫到終端的字符流都被認為是是一個UTF8字符流.所以,編程的時候,從外部(文件或者控制台)讀入UTF8字符流,轉換為wchar_t,然後程序在內部使用寬字符處理,最後再把要輸出的寬字符流轉換為UTF8字符流並輸出到控制台/文件中. 用戶程序可以通過環境變量LANG的值得知當前的系統環境所使用的字符編碼.由此可見,C庫函數的 mbstowcs()/wcstombs()主要是為應付這種情況設計的. 如果要處理XML, HTML 等等有明確指明字符編碼的字符流,用專門的字符轉換庫更為方便.

為什麼很多Windows下的C源文件的注釋在Linux編輯器下會顯示為亂碼就很好理解了.

 

3. 字符編碼轉換相關的函數和庫

 

Windows 的字符轉換函數: MultiByteToWideChar() / WideCharToMultiByte()

Linux 的字符轉換庫: GLIBC iconv函數組.

 

C標准庫使用的 mbstowcs()和wcstombs()和 locale 相關,用起來很不方便,而且功能有限.

 

(注意不要假設 wchar_t 的大小, 它可能是4字節也可能是2字節,取決於編譯器. 比如 MS VC9.0 (2008) 裡, sizeof(wchar_t) = 2, 而在GCC中, sizeof(wchar_t) = 4.)

 

4. 給定一個ANSI兼容的字符串(包括GB2312,GBK,UTF8等),無法確定它的編碼類型,只能猜測.所以不要指望會有一個萬能的轉換函數.

 

5. BOM (Byte Order Mark)UNICODE: FF FE / FE FF 和 UTF8: EF BB BF 是不完全靠譜的,僅供參考.

 

最後說明一點,對於不是專門處理字符編碼的程序來說,所有字符編碼相關的問題只是顯示的問題,並不會影響到程序的內在邏輯.

開始用 Unicode 來編寫我們的代碼吧.

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved