轉自: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的證實可以使用如下代碼:
-
-
-
int main()
-
{
-
using namespace std;
-
-
ofstream OutFile("OutInUTF8.txt");
-
OutFile << "你" << endl;
-
OutFile.close();
-
-
return 0;
-
}
以上代碼在活動代碼頁為GBK的Window中生成的txt文件的編碼是UTF8。
對規則2的證實可以使用如下代碼:
-
-
-
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進行編碼保存
可用以下程序進行驗證
-
-
-
-
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;
-
}
-
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)
-
{
-
std::wstring_convert<chs_codecvt> converter;
-
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; }