目錄
問題
字符集和編碼往往是IT菜鳥甚至是各種大神的頭痛問題。當遇到紛繁複雜的字符集,各種火星文和亂碼時,問題的定位往往變得非常困難。本文就將會從原理方面對字符集和編碼做個簡單的科普介紹,同時也會介紹C++中設置字符串編碼以及編碼轉換的一些方法。
字符集
簡單的說,字符集就規定了某個文字對應的二進制數字存放方式(編碼)和某串二進制數值代表了哪個文字(解碼)的轉換關係。常見的字符集如ASCII、GB2312、GBK等。
下面就是屌
這個字在各種編碼下的十六進制和二進制編碼結果
字符編碼
字符集只是一個規則集合的名字,對應到真實生活中,字符集就是對某種語言的稱呼。例如:英語,漢語,日語。
對於一個字符集來說要正確編碼轉碼一個字符需要三個關鍵元素:字庫表、編碼字符集、字符編碼。
字庫表是一個相當於所有可讀或者可顯示字符的數據庫,字庫表決定了整個字符集能夠展現表示的所有字符的範圍。
編碼字符集,即用一個編碼值來表示一個字符在字庫中的位置。
字符編碼定義編碼字符集和實際存儲數值之間的轉換關係。一般來說,都會直接將字符的編碼值直接進行存儲。例如在ASCII中A
在表中排第65位,而編碼後A
的數值是0100 0001
也即十進制的65的二進制轉換結果。
字符集與字符編碼的關係
一般一個字符集等同於一個編碼方式,ANSI體系的字符集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。一般我們說一種編碼都是針對某一特定的字符集。
一個字符集上也可以有多種編碼方式,例如UCS字符集(也是Unicode使用的字符集)上有UTF-8、UTF-16、UTF-32等編碼方式。
多種字符編碼存在的意義
既然字庫表中的每一個字符都有一個自己的序號,直接把序號作爲存儲內容就好了。爲什麼還要多此一舉通過字符編碼
把序號轉換成另外一種存儲格式呢?
統一字庫表的目的是爲了能夠涵蓋世界上所有的字符,但實際使用過程中會發現真正用的上的字符相對整個字庫表來說比例非常低。例如中文地區的程序幾乎不會需要日語字符,而一些英語國家甚至簡單的ASCII字庫表就能滿足基本需求。而如果把每個字符都用字庫表中的序號來存儲的話,每個字符就需要3個字節(這裏以Unicode字庫爲例),這樣對於原本用僅佔一個字符的ASCII編碼的英語地區國家顯然是一個額外成本(存儲體積是原來的三倍)。於是就出現了UTF-8這樣的變長編碼。在UTF-8編碼中原本只需要一個字節的ASCII字符,仍然只佔一個字節。而像中文及日語這樣的複雜字符就需要2個到3個字節來存儲。
字符編碼的發展歷史
第一個階段:ASCII字符集和ASCII編碼
計算機剛開始只支持英語(即拉丁字符),其它語言不能夠在計算機上存儲和顯示。ASCII用一個字節(Byte)的7位(bit)表示一個字符,第一位置0。後來爲了表示更多的歐洲常用字符又對ASCII進行了擴展,又有了EASCII,EASCII用8位表示一個字符,使它能多表示128個字符,支持了部分西歐字符。
第二個階段:ANSI編碼(本地化)
爲使計算機支持更多語言,通常使用 0x80~0xFF 範圍的 2 個字節來表示 1 個字符。比如:漢字 ‘中’ 在中文操作系統中,使用 [0xD6,0xD0] 這兩個字節存儲。
不同的國家和地區制定了不同的標準,由此產生了 GB2312, BIG5, JIS 等各自的編碼標準。這些使用 2 個字節來代表一個字符的各種漢字延伸編碼方式,稱爲 ANSI 編碼。在簡體中文系統下,ANSI 編碼代表 GB2312 編碼,在日文操作系統下,ANSI 編碼代表 JIS 編碼。
不同 ANSI 編碼之間互不兼容,當信息在國際間交流時,無法將屬於兩種語言的文字,存儲在同一段 ANSI 編碼的文本中。
第三個階段:UNICODE(國際化)
爲了使國際間信息交流更加方便,國際組織制定了 UNICODE 字符集,爲各種語言中的每一個字符設定了統一併且唯一的數字編號,以滿足跨語言、跨平臺進行文本轉換、處理的要求。UNICODE 常見的有三種編碼方式:UTF-8(1-4個字節表示)、UTF-16((2個字節表示))、UTF-32(4個字節表示)。
活動代碼頁
代碼頁是字符集編碼的別名,也有人稱"內碼錶"。
早期,代碼頁是IBM稱呼電腦BIOS本身支持的字符集編碼的名稱。當時通用的操作系統都是命令行界面系統,這些操作系統直接使用BIOS供應的VGA功能來顯示字符,操作系統的編碼支持也就依靠BIOS的編碼。現在這BIOS代碼頁被稱爲OEM代碼頁。圖形操作系統解決了此問題,圖形操作系統使用自己字符呈現引擎可以支持很多不同的字符集編碼。
早期IBM和微軟內部使用特別數字來標記這些編碼,其實大多的這些編碼已經有自己的名稱了。雖然圖形操作系統可以支持很多編碼,很多微軟程序還使用這些數字來點名某編碼。
下表列出了部分代碼頁及其國家(地區)或者語言:
代碼頁 國家(地區)或語言
437 美國
932 日文(Shift-JIS)
936 中國 - 簡體中文(GB2312)
950 繁體中文(Big5)
1200 Unicode
1201 Unicode (Big-Endian)
50220 日文(JIS)
50225 韓文(ISO)
50932 日文(自動選擇)
50949 韓文(自動選擇)
52936 簡體中文(HZ)
65000 Unicode (UTF-7)
65001 Unicode (UTF-8)
c++的多字節字符與寬字節字符
C++基本數據類型中表示字符的有兩種:char、wchar_t。
char叫多字節字符,一個char佔一個字節,之所以叫多字節字符是因爲它表示一個字時可能是一個字節也可能是多個字節。一個英文字符(如’s’)用一個char(一個字節)表示,一箇中文漢字(如’中’)用2個char(二個字節)表示,看下面的例子。
void TestChar()
{
char ch1 = 's'; // 正確
cout << "ch1:" << ch1 << endl;
char ch2 = '中'; // 錯誤,一個char不能完整存放一個漢字信息
cout << "ch2:" << ch2 << endl;
char str[3] = "中"; //前二個字節存放漢字'中',最後一個字節存放字符串結束符\0
cout << "str:" << str << endl;
}
c++的多字節字符串與寬字節字符串
字符數組可以表示一個字符串,但它是一個定長的字符串,我們在使用之前必須知道這個數組的長度。爲方便字符串的操作,STL爲我們定義好了字符串的類string和wstring。大家對string肯定不陌生,但wstring可能就用的少了。
string是普通的多字節版本,是基於char的,對char數組進行的一種封裝。
wstring是Unicode版本,是基於wchar_t的,對wchar_t數組進行的一種封裝。
###string 與 wstring的相關轉換:
以下的兩個方法是跨平臺的,可在Windows下使用,也可在Linux下使用。
C++程序輸出字符串的編碼
C++程序輸出的字符串編碼規則如下:
- 若程序中指定了字符串的編碼,則輸出字符串的編碼與指定編碼相同;
- 若程序中沒有指定字符串的編碼,則輸出字符串的編碼與程序在編譯時Windows代碼頁編碼相同(Linux情況尚未實驗);
- 程序輸出字符串的編碼與程序源文件編碼無關;
對規則1的證實可以使用如下代碼:
#include <fstream>
int main()
{
using namespace std;
ofstream OutFile("OutInUTF8.txt");
OutFile << "你" << endl;
OutFile.close();
return 0;
}
以上代碼在活動代碼頁爲GBK的Window中生成的txt文件的編碼是UTF8。
對規則2的證實可以使用如下代碼:
#include <fstream>
int main()
{
using namespace std;
ofstream OutFile("Output.txt");
OutFile << "你" << endl;
OutFile.close();
return 0;
}
上面的程序如果編譯時Window的代碼頁是936(GBK),結果生成的txt文件編碼也是GBK。即使將該程序在Window的代碼頁爲65001(UTF8)的Windows系統中運行,生成的txt文件編碼依然是GBK。
對規則3的證實:
仍然使用上面的程序。但是其源文件編碼設成UTF8,然後在代碼頁是936(GBK)的Window系統上編譯,結果生成的txt文件編碼是GBK。
字符串常量
C++98標準中一些表示字符串常量的標識有:
- "您好": string字符串常量(字節數組),使用當前系統代碼頁進行編碼
C++11標準中增加了一些表示字符串常量的標識,如下有:
- L"您好!": wstring字符串常量,使用文件保存編碼方式字符集
- R"(您好 \n)": 原始字符串常量(字節數組),保留所有的字符
- u8"您好!": string字符串常量(字節數組),使用UTF8進行編碼保存
可用以下程序進行驗證
#include <string>
int main()
{
using namespace std;
string str;
wstring wstr;
str = "你好";
wstr = "你好"; //編譯報錯——“初始化”: 無法從“const char [3]”轉換爲“std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t>>”
str = L"你好"; //編譯報錯——無法從“const wchar_t [2]”轉換爲“std::basic_string<char,std::char_traits<char>,std::allocator<char>>”
wstr = L"你好";
str = R"(你好)";
wstr = R"(你好)";//編譯報錯——二進制“=”: 沒有找到接受“const char [5]”類型的右操作數的運算符(或沒有可接受的轉換)
return 0;
}