編碼 utf8 gbk Unicode
轉自:https://my.oschina.net/shelllife/blog/1827897
C/C++語言中的字元類型
存在兩種表示字元的基本類型:
- char:一個位元組8bit表示,最多表示256個字元,表示和用來處理ASCII字元集,國際通用
- wchar_t:多位元組字元表示,典型2個位元組或者4個位元組,如GNU libc中為4B,可以表示更多的字元,滿足國際化應用開發的需求,實作标準
在開發中ASCII編碼字元都是用char來表示,可以轉換成wchar_t表示;wchar_t類型與Unicode編碼是完全獨立的概念,不過在實作上Unicode編碼一般用wchar_t來表示實作而已,但wchar_t字元并不一定就是Unicode編碼字元。
對應兩種字元類型存在兩種字元串類型(C++):
- string: char字元清單或者是位元組清單(bytes)
- wstring: wchar_t字元清單或者是寬子節清單
對應兩種字元類型的輸出函數流對象有:
- sprintf/wsprintf: 分别對應char與wchar_t
- cout/wcout:分别對應string與wstring
- stringstream/wstringstream: 分别對應string與wstring
字元串常量
C++11标準中增加了一些表示字元串常量的辨別,如下有:
- L"您好!": wstring字元串常量,使用檔案儲存編碼方式字元集
- R"(您 好 \n)": 原始字元串常量(位元組數組),保留所有的字元
- u8"您好!": string字元串常量(位元組數組),使用UTF8進行編碼儲存
字元集及編碼
已知有很多的字元集,比如:ASCII,UTF8,GBK,GB2312,UTF16,UTF32,UNICODE,Latin等等。常用中文字元集編碼有:
- UTF8:又分為帶簽名和不帶簽名兩種,Windows代碼頁為65001,VS中應該選擇【UTF8-帶簽名】的格式
- GBK/GB2312:Windows代碼頁為936
- GB18030: Windows代碼頁為54936
小技巧:修改Windows系統中cmd指令行視窗的顯示字元集,預設字元集為OS字元集,如GBK-936。如果希望顯示UTF8字元,則可以修改:chcp 65001.
在VS項目的調試指令視窗中無法手動修改,可以通過system函數來修改:
system("C:\\Windows\\system32\\chcp 65001");// 修改終端字元集為UTF8
源檔案的編碼儲存選項
可以将源檔案儲存成不同的編碼方式,如果檔案中有中文,則必須選擇可以對中文進行編碼的字元集,如UTF8,GBK,GB2312等,否認可能會出現莫名其妙的編譯錯誤,因為檔案中存在無法識别的字元内容。
在Visual Studio中,選中檔案後該設定在:【檔案 - 進階儲存選項】中。
C++11中GBK/UTF/wchar_t之間的編碼處理轉換
在進行中文時,不同的應用場景下總是無法避免進行GBK和UTF8之間的互相轉換,C++11辨別提供了<locale>和<codecvt>機制和工具來簡化處理。
借助其中的std::wstring_convert和std::codecvt_utf8模闆,通過wchar_t類型為中介,可以快速地實作轉換,基本代碼如下:
/*
轉換GBK編碼的中文到UTF8編碼:GBK - WChar - UTF8兩次轉換
*/
//GBK在linux下的locale名可能是"zh_CN.GBK"
const char* GBK_LOCALE_NAME = ".936"; //GBK在windows下的locale name
std::wstring_convert<std::codecvt_byname<wchar_t, char, mbstate_t>> Conver_GBK(new codecvt_byname<wchar_t, char, mbstate_t>(GBK_LOCALE_NAME)); //GBK - wchar_t
std::wstring _wname = Conver_GBK.from_bytes(data.Name); // 輸入為char*的字元串表示或者數組,輸出為wstring
std::wstring _waddr = Conver_GBK.from_bytes(data.Address);// 輸入為char*的字元串表示或者數組,輸出為wstring
std::wstring _wdept = Conver_GBK.from_bytes(data.GrantDept);// 輸入為char*的字元串表示或者數組,輸出為wstring
wcout << "Name: " << _wname << ",addr:" << _waddr << ",dept:" << _wdept << endl;
// 将wstring轉化為UTF8編碼的位元組數組string表示
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
string _name = conv.to_bytes(_wname);// 輸入為wstring字元串
string _addr = conv.to_bytes(_waddr);
string _dept = conv.to_bytes(_wdept);
// 将UTF8字元串轉換為GBK字元編碼表示string位元組數組
std::string _name = Conver_GBK.to_bytes(conv.from_bytes(_name)); // 先轉換成wstring,然後再轉換成GBK的string
std::string _addr = Conver_GBK.to_bytes(conv.from_bytes(_addr)); // 先轉換成wstring,然後再轉換成GBK的string
https://blog.csdn.net/xuyouqiang1987/article/details/102869440
字元集
簡單的說,字元集就規定了某個文字對應的二進制數字存放方式(編碼)和某串二進制數值代表了哪個文字(解碼)的轉換關系。常見的字元集如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的證明可以使用如下代碼:
- ofstream OutFile("Output.txt");
上面的程式如果編譯時Window的代碼頁是936(GBK),結果生成的txt檔案編碼也是GBK。即使将該程式在Window的代碼頁為65001(UTF8)的Windows系統中運作,生成的txt檔案編碼依然是GBK。
對規則3的證明:
仍然使用上面的程式。但是其源檔案編碼設成UTF8,然後在代碼頁是936(GBK)的Window系統上編譯,結果生成的txt檔案編碼是GBK。
C++98标準中一些表示字元串常量的辨別有:
- "您好": string字元串常量(位元組數組),使用目前系統代碼頁進行編碼
- R"(您好 \n)": 原始字元串常量(位元組數組),保留所有的字元
可用以下程式進行驗證
- #include <string>
- 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]”類型的右操作數的運算符(或沒有可接受的轉換)
C++11字元串換初始化方式
- char16_t* p1 = u"中國";//把字元串初始化為UTF16字元串存儲
- char32_t* p2 = U"中國";//把字元串初始化為UTF32字元串存儲
- wchar_t* p3 = L"中國";//win是UCS2碼下等同UTF16字元串,Linxu是UCS4碼下等同utf32字元串
- char* p4 = u8"中國";//把字元串初始化為UTF8字元串存儲
- char* p5 = "中國";//p5字元的編碼格式見我的上篇博文《c++字元串亂碼研究》
- std::string str1 = u8"中國";
- std::wstring str2 = L"中國";
- std::u16String str3 = u"中國";
- std::u32String str4 = U"中國";
可以看到以下字元串轉換都是先轉 std::wstring,即先轉成字元的UCS碼
std::string 轉為 std::wstring( utf-8 --> wchar )
- //src必須是UTF8否則抛異常
- std::wstring utf8_to_wstr(const std::string& src)
- std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
- return converter.from_bytes(src);
std::wstring轉為std::string(wchar --> utf-8)
- std::string wstr_to_utf8(const std::wstring& src)
- std::wstring_convert<std::codecvt_utf8<wchar_t>> convert;
- return convert.to_bytes(src);
在構造std::wstring_convert對象的時候需要傳入一個codecvt對象的指針,如果沒有傳入,則預設使用
new codecvt
來建立。std::wstring_convert自行維護codecvt對象的生命周期,它的析構函數會調用delete操作符來删除該對象。這就限制了隻能使用通過new操作符來建立的codecvt,而不能使用從std::locale中擷取的codecvt。
在C++标準提供的codecvt中,能夠直接用于std::wstring_convert的隻有三個:std::codecvt_utf8,std::codecvt_utf16以及std::codecvt_utf8_utf16。可見,标準隻支援UTF族的字元編碼。為了擷取其它字元編碼的codecvt,需要使std::codecvt_byname,這個類可以通過字元編碼的名稱來建立一個codecvt。這看起來挺不錯,但遺憾的是,字元編碼的名稱并沒有統一的标準,各個平台的支援情況都不一樣。例如,在Windows下可以使用“chs”或“.936”來建立簡體中文編碼的codecvt,linux下可是"zh_CN.GBK"在Mac OS X下則要使用“zh_cn.gb2312”;甚至在Mac OS X下,即使成功建立了這個codecvt,它也不能正常地轉換。
由于曆史原因,std::codecvt_byname的析構函數是protected的(g++是,VC好像不是),std::wstring_convert不能對它調用delete,是以首先要自行定義一個類來繼承std::codecvt_byname:
- class chs_codecvt : public std::codecvt_byname<wchar_t, char, std::mbstate_t> {
- public:
- chs_codecvt() : codecvt_byname("chs") { }//zh_CN.GBK or .936
- };
std::wstring 轉GBK(wchar --> ansi)
- std::string wstr_to_gbk(const std::wstring& str)
- std::wstring_convert<chs_codecvt> converter;
- return converter.to_bytes(str);
GBK 轉為 std::wstring( ansi --> wchar )
- std::wstring gbk_to_wstr(const std::string& str)
- return converter.from_bytes(str);
#include<string>
#include<windows.h>
#include<vector>
using namespace std;
//utf8 轉 Unicode
std::wstring Utf82Unicode(const std::string& utf8string)
{
int widesize = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, NULL, 0);
if (widesize == ERROR_NO_UNICODE_TRANSLATION)
{
throw std::exception("Invalid UTF-8 sequence.");
}
if (widesize == 0)
{
throw std::exception("Error in conversion.");
}
std::vector<wchar_t> resultstring(widesize);
int convresult = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, &resultstring[0], widesize);
if (convresult != widesize)
{
throw std::exception("La falla!");
}
return std::wstring(&resultstring[0]);
}
//unicode 轉為 ascii
std::string WideByte2Acsi(std::wstring& wstrcode)
{
int asciisize = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, NULL, 0, NULL, NULL);
if (asciisize == ERROR_NO_UNICODE_TRANSLATION)
{
throw std::exception("Invalid UTF-8 sequence.");
}
if (asciisize == 0)
{
throw std::exception("Error in conversion.");
}
std::vector<char> resultstring(asciisize);
int convresult =::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, &resultstring[0], asciisize, NULL, NULL);
if (convresult != asciisize)
{
throw std::exception("La falla!");
}
return std::string(&resultstring[0]);
}
//utf-8 轉 ascii
std::string UTF_82ASCII(std::string& strUtf8Code)
{
std::string strRet("");
//先把 utf8 轉為 unicode
std::wstring wstr = Utf82Unicode(strUtf8Code);
//最後把 unicode 轉為 ascii
strRet = WideByte2Acsi(wstr);
return strRet;
}
///
//ascii 轉 Unicode
std::wstring Acsi2WideByte(std::string& strascii)
{
int widesize = MultiByteToWideChar (CP_ACP, 0, (char*)strascii.c_str(), -1, NULL, 0);
if (widesize == ERROR_NO_UNICODE_TRANSLATION)
{
throw std::exception("Invalid UTF-8 sequence.");
}
if (widesize == 0)
{
throw std::exception("Error in conversion.");
}
std::vector<wchar_t> resultstring(widesize);
int convresult = MultiByteToWideChar (CP_ACP, 0, (char*)strascii.c_str(), -1, &resultstring[0], widesize);
if (convresult != widesize)
{
throw std::exception("La falla!");
}
return std::wstring(&resultstring[0]);
}
//Unicode 轉 Utf8
std::string Unicode2Utf8(const std::wstring& widestring)
{
int utf8size = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, NULL, 0, NULL, NULL);
if (utf8size == 0)
{
throw std::exception("Error in conversion.");
}
std::vector<char> resultstring(utf8size);
int convresult = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, &resultstring[0], utf8size, NULL, NULL);
if (convresult != utf8size)
{
throw std::exception("La falla!");
}
return std::string(&resultstring[0]);
}
//ascii 轉 Utf8
std::string ASCII2UTF_8(std::string& strAsciiCode)
{
std::string strRet("");
//先把 ascii 轉為 unicode
std::wstring wstr = Acsi2WideByte(strAsciiCode);
//最後把 unicode 轉為 utf8
strRet = Unicode2Utf8(wstr);
return strRet;
}