功能描述:最全的 Qt 字符編碼相關知識以及中文亂碼的原因與解決辦法
一、字符編碼種類
ASCII?碼
美國人對信息交流的編碼,包括 26 個字母(大小寫)、數字和標點符號等,用一個字節(8 位)表示這些字符,實際只編到了第 127 個,這就是 ASCII (American Standard Code for Information Interchange) 編碼。
ASCII?擴展碼
計算機傳到其他國家,發現有些本國的字符在 ASCII 碼中沒有,就將這些字符編到 ASCII 剩余的位置(128 到 255),這就是 ASCII 擴展碼。
gb2312
中文常用的漢字就 6000 多個,中國人采用 gb2312 編碼,使用兩個字節(16 位),有 65536 個編碼位。前 127 個和 ASCII 一樣,后面的是漢字的編碼。原先的 ASCII 就是半角符號,后來新編的就是全角符號。“英文占一個字節,漢字占兩個字節”,說的就是這套編碼。
gbk
gb2312 + 生僻字 = gbk
gb10830
gbk + 少數民族文字 = gb10830
Unicode
國際標準化組織制定出一套包含全世界所有語言文字的編碼,大家都用這套編碼,免得亂七八糟的,誰也不認識誰,這套編碼就是 Unicode。
utf
Unicode 只是一套標準,它只是規定了數字多少對應哪個字符,但它不是計算機內部可用的具體實現,就需要有給具體的方案,將數字和字符的對照表輸入到計算機內讓機器也認識,utf 就是 Unicode Transformation Format 的縮寫,意為 Unicode 轉換格式。
utf24
Unicode 最長的編碼需要 24 位,用 3 個字節表示一個字符,這個方案就被稱為 utf24。這個想法好是好,但是不符合實際情況。計算機一次讀取的字節只能是 1、2、4、8 這樣子,不能一次讀 3個字節,所以 24 位的方案很快就被放棄了。
utf32
用 4 個字節表示一個字符,這個方案就被稱為 utf32。優點是簡單明確,不管是什么字符,全都按 4 位讀取,簡單明了。但缺點是浪費空間,本來最多 3 個字節就夠了,但這每個字符至少浪費了一個字節。像英文這種一個字節就夠,最低位是具體內容,其余 3 位都是 0,直接有 3 位是浪費的,也就是說一個 1GB 的英文文檔,需要 4GB 的存儲空間,生生的浪費的 3 倍。
utf16
有人提出用 16 位就夠了,那些一個字節的兩個字節的字符直接用,三個字節的通過某種算法可以使用兩個字節來實現。使用 16 位來實現 Unicode 的碼表,這套方案就是 utf16。
utf8
utf8 是一個變長方案,對于英文,只要一個字節就行,漢字則需要 3 個字節。
二、char * 和 QString 編碼問題
char * 類型字符串是什么編碼?
對于程序內部聲明的字符串,char * 的編碼取決于程序源文件的編碼,源文件是什么編碼,字符串就是什么編碼。
對于程序外部傳入的字符串,比如從 argv 中讀取的字符串,其編碼與源文件的編碼無關,只看當前操作系統的編碼。
QString?是什么編碼?
Qt 的文檔中明確指出,QString?內部使用的是?Unicode?編碼,具體實現方案是?utf16。在使用char * 構造 QString 實例的時候,QString 默認將 char * 當作是 utf8 編碼去構造,即:QString s(char *) 等價于 QString s = QString::fromUtf8(char*)。
同樣是使用 char * 構造 QString,為什么有的時候是亂碼,有的時候就不是亂碼?這取決于你的char * 是什么編碼了,如果是 utf8 的,就不會有亂碼問題,如果是 gbk 的,就會出現亂碼的情況。
三、字符串長度
QString 字符串長度
只要 QString 能夠正常構造,沒有亂碼,不管是英文還是中文,都占一個長度。別忘了,QString 用的是 utf16 方案,一個長度占兩個字節。
char * 字符串長度
char * 的長度需要看具體的編碼了,gbk 是英文一個長度,漢字兩個長度,這里一個長度占一個字節。utf8 是英文一個長度,漢字三個長度,這里的一個長度也是一個字節。
所以,不要太相信你的 strlen 函數,關鍵是得看你是什么編碼。
四、QTextCodec::setCodecForLocale?的真正含義
這個函數真正的作用只是告訴你的程序,當前程序內部使用的字符編碼是什么。這個函數無法對外部傳入和內部聲明的字符串產生影響,和系統的編碼或者源文件的編碼無關。
經測試,這個函數只對 QString 的 Local8bit 函數有影響。如果用這個函數將程序編碼設置為 gbk,那么 Local8bit 就是 gbk,如果設置的編碼是 utf8,Local8bit 就是 utf8。用了這個函數解決了中文亂碼問題一定是因為使用了 QString 的 toLocal8bit 或者 fromLocal8bit,否則這個函數不會對中文亂碼問題有任何影響。
假設你的系統是 gbk 的編碼,進程默認使用的編碼也是 gbk,這時候拿到一個 utf8 的 char *,然后用 fromLocal8bit 來構造 QString,這時候的 Local8bit 是 gbk,但 char 是 utf8,用 gbk 的規則去構造 utf8 的編碼就亂碼了。
五、char *?轉?QString
utf8?的?char * 轉 QString
如果 char *是 utf8 的,QString 默認將 char * 當 utf8 處理,用 QString s(char *) 或者 QString s = QString::fromUtf8(char*) 直接構造就行了。
gbk?的?char * 轉 QString
如果 char * 是 gbk 的,需要先用 QTextCodec::setCodecForLocale 將 Local8bit 設置成 gbk,再使用 fromLocal8bit 來構造。
QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));
QString s = QString::fromLocal8bit(char*);
不確定編碼的 char * 轉 QString
在實際編程的時候,經常不確定這個 char * 是 utf8 的還是 gbk 的,這時候可以用下面的方法進行構造。這段代碼的含義是,先將 char * 轉成 utf8,看是否成功,不成功再轉成 gbk。
char * c = "abcdefg";
QTextCodec::ConverterState state;
QString s = QTextCodec::codecForName("UTF-8")->toUnicode(c, strlen(c), &state);
if(state.invalidChars > 0)
{s = QTextCodec::codecForName( "GBK" )->toUnicode(c);
}
?在 Qt5 中使用 QString::QStringLiteral 這個宏來處理中文亂碼問題,這個宏是官方提供專門處理亂碼問題的。
tr() 不是處理中文亂碼的,這個函數是用來處理多語言問題的。
六、Windows?系統本地字符集編碼
在 windows 命令行終端模式下,輸入命令:chcp
如上圖,我的活動代碼頁為 936,意思是“中國-簡體中文 (GB2312)"
下表列出了所有支持的代碼頁及其國家(地區)或者語言:
代碼頁 | 國家(地區)或語言 |
---|---|
437 | 美國 |
708 | 阿拉伯文(ASMO 708) |
720 | 阿拉伯文(DOS) |
850 | 多語言(拉丁文 I) |
852 | 中歐(DOS)-斯拉夫語(拉丁文 II) |
855 | 西里爾文(俄語) |
857 | 土耳其語 |
860 | 葡萄牙語 |
861 | 冰島語 |
862 | 希伯來文(DOS) |
863 | 加拿大-法語 |
865 | 日耳曼語 |
866 | 俄語-西里爾文(DOS) |
869 | 現代希臘語 |
874 | 泰文(Windows) |
932 | 日文(Shift-JIS) |
936 | 中國-簡體中文(GB2312) |
949 | 韓文 |
950 | 繁體中文(Big5) |
1200 | Unicode |
1201 | Unicode (Big-Endian) |
1250 | 中歐(Windows) |
1251 | 西里爾文(Windows) |
1252 | 西歐(Windows) |
1253 | 希臘文(Windows) |
1254 | 土耳其文(Windows) |
1255 | 希伯來文(Windows) |
1256 | 阿拉伯文(Windows) |
1257 | 波羅的海文(Windows) |
1258 | 越南文(Windows) |
20866 | 西里爾文(KOI8-R) |
21866 | 西里爾文(KOI8-U) |
28592 | 中歐(ISO) |
28593 | 拉丁文 3 (ISO) |
28594 | 波羅的海文(ISO) |
28595 | 西里爾文(ISO) |
28596 | 阿拉伯文(ISO) |
28597 | 希臘文(ISO) |
28598 | 希伯來文(ISO-Visual) |
38598 | 希伯來文(ISO-Logical) |
50000 | 用戶定義的 |
50001 | 自動選擇 |
50220 | 日文(JIS) |
50221 | 日文(JIS-允許一個字節的片假名) |
50222 | 日文(JIS-允許一個字節的片假名-SO/SI) |
50225 | 韓文(ISO) |
50932 | 日文(自動選擇) |
50949 | 韓文(自動選擇) |
51932 | 日文(EUC) |
51949 | 韓文(EUC) |
52936 | 簡體中文(HZ) |
65000 | Unicode (UTF-7) |
65001 | Unicode (UTF-8) |
.cpp 或 .h 文件從 window 上傳到 Ubuntu 后會顯示亂碼,原因是因為 ubuntu 環境設置默認是 utf-8,Windows 系統本地字符集默認編碼為 GBK。
在簡體中文 windows 系統下,ANSI編碼代表?GBK/GB2312?編碼。
七、中文亂碼現象
如果在?Qt Creator?中無法輸入中文,則選擇“編輯”>“Select Encoding…”>“GBK…”或“UTF8+BOM”,點擊“按編碼重新載入”,此時就可以輸入中文了。
列舉最常用的 3 個編譯器(微軟?VC++?的?cl?編譯器,MinGW?中的?g++,Linux?下的?g++),源代碼分別采用 GBK 和無 BOM 的 UTF-8 以及有 BOM 的 UTF-8 這 3 種編碼進行保存,發生的現象如下表所示:? ? ? ?
★★★★★
情況1:指的是 Local 字符集為 GBK 情況2:指的是 Local 字符集為 UTF-8 | |||
源代碼的編碼 | 編譯器 | 顯示正常 | 顯示亂碼 |
GBK | win vs cl | 情況1 | 情況2 |
win MinGW-g++ | 情況1 | 情況2 | |
linux g++ | 情況1 | 情況2 | |
UTF-8(無 BOM) | win vs cl | 編譯失敗 error C2001:常量中有換行符 | 編譯失敗 error C2001:常量中有換行符 |
win MinGW-g++ | 情況2 | 情況1 | |
linux g++ | 情況2 | 情況1 | |
UTF-8(有 BOM) | win vs cl | 情況1 情況2(有 #pragma 預處理) | 情況2(有 #pragma 預處理) |
win MinGW-g++ | 情況2 | 情況1 | |
linux g++ | 情況2 | 情況1 |
源代碼的編碼:“菜單” -> “工具” -> “選項” -> “文本編輯器” -> “行為” -> “文件編碼” -> “默認編碼”
常用的選項有以下幾個:
System?(簡體中文 windows 系統默認指的是 GBK 編碼)
GBK/windows-936-2000/CP936/MS936/windows-936
UTF-8
Local?字符集:取決于?QTextCodec * codec = QTextCodec::codecForName();
當使用 Visual C++ 編譯程序的時候,它會分析源文件采用何種編碼,有 BOM 標識符則可以正確識別其編碼是 UTF-8,若沒有 BOM 標識符則認為其使用本地字符集編碼(Local 字符集)。
如果源文件是 UTF-8+BOM 的編碼方式,還需要在頭文件加入:
#if defined(_MSC_VER) && (_MSC_VER >= 1600)
#pragma execution_character_set("utf-8")
#endif
或者在 .pro 文件中添加:
QMAKE_CXXFLAGS += /utf-8
如果源文件是 UTF-8+無 BOM 的編碼方式,則一定不能加 #pragma execution_character_set(“utf-8”),不然會產生亂碼。
當 QTextCodec::codecForName("utf-8") 時:
QString::fromLocal8Bit 和 QString::fromUtf8 是等效的。
當 QTextCodec::codecForName("gbk") 時:
QString::fromLocal8Bit 和 QString::fromUtf8 是不等效的。
★★★★★
如果該工程不需要跨平臺使用(只在 Win),那么工程設置請使用?GBK?的編碼方式;
如果該工程要跨平臺使用(Win+Linux),那么工程設置請使用?UTF-8+BOM?的編碼方式,Local?字符集設置為?UTF-8。
UTF-8 和 GBK 其實對英文和數字都是一樣的 ASCII 單字節編碼,所以源文件用英文和數字是肯定不亂碼,主要是漢字之類的本地語言文字編碼顯示容易出錯。
Windows?系統里一般的記事本、編輯器、VC++?開發環境等都是默認用?GBK?漢字編碼,而? Linux?和?Qt?都是默認用 UTF-8?國際文字編碼,所以文本顯示亂碼一般都是這個原因。
八、“UTF-8 無 BOM 格式”和 “UTF-8 帶 BOM 格式”的區別
BOM—Byte Order Mark,就是字節序標記。
UTF-8 帶 BOM 格式,就是在文件頭添加了 3 個 bits 的?b'\xef\xbb\xbf'字符。
通常編程,特別是 Linux 下編程建議使用 “UTF-8 無 BOM 格式”,這種不含 BOM 的 UTF-8 才是標準形式,由于含有 BOM 的 UTF-8 常常和 Linux 系統經常使用的 #! 沖突。
若是在 windows 下編程,建議使用 “UTF-8 帶 BOM 格式”,這樣比較好。
如今只有微軟還在堅持使用帶 BOM 格式的 UTF-8,由于它便于較快的與不少本地編碼,如 gbk,ascii 相區分。總之,微軟為了向前兼容性,一直堅持使用帶 BOM 格式的 UTF-8。
九、查看?Qt Creator?源文件的編碼格式
點擊菜單 “工具” -> “選項” -> “文本編輯器”,右邊選擇“顯示”,看到下圖:
選中 “Display file encoding”,然后點擊 “OK” 按鈕,就可以在編輯器右上角看到當前文件的編碼格式:
十、解決中文亂碼的方法
總結下,解決中文亂碼的方法:
其中,源代碼的編碼:“菜單” -> “工具” -> “選項” -> “文本編輯器” -> “行為” -> “文件編碼” -> “默認編碼”;Local字符集:取決于QTextCodec *codec = QTextCodec::codecForName();
必須保證源文件的編碼和顯示文字的編碼保持一致,否則中文亂碼。
放了方便起見,通常將源文件的編碼和顯示文字的編碼設置保持一致。
如果沒有設置?Local?字符集,則使用默認系統的字符編碼方式,windows?下是?GBK?編碼,Linux?下是?UTF-8?編碼。
直接輸入文字,QString?默認將?char *?當作是?UTF-8?編碼去構造。
情況1:如果是msvc 2013/2015/2017等編譯器
(1) 源文件編碼設置為?GBK
- Local字符集設置為 GBK
采用 QString::fromLocal8Bit() 對中文進行轉碼,則顯示正常,否則亂碼;因為設置了本地字符集為 GBK 編碼,fromLocal8Bit() 就是將中文轉換為 GBK 編碼。
- 沒有設置 Local?字符集
如果沒有設置 Local 字符集編碼方式,則 Qt 采用默認的系統字符編碼方式 GBK,和上述 “Local字符集設置為GBK” 轉碼方法相同。
- Local?字符集設置為其它編碼方式,如?UTF-8
中文顯示亂碼,因為無法將顯示文字的編碼方式設為 GBK。
(2) 源文件編碼設置為?UTF-8(無?BOM)
大概率報錯:編譯失敗,error C2001: 常量中有換行符
有時候能顯示出來,如果沒報錯:
- Local?字符集設置為?UTF-8
直接輸入中文(QString 默認將 char * 當作是 UTF-8 編碼去構造)、采用 QString::fromLocal8Bit()(此時本地字符集為 UTF-8)、采用 QString::fromUtf8() 都能顯示正常。
- 沒有設置?Local?字符集
如果沒有設置 Local 字符集編碼方式,則 Qt 采用默認的系統字符編碼方式 GBK,QString::fromLocal8Bit()(此時本地字符集為 GBK)則顯示亂碼,直接輸入、采用QString::fromUtf8() 都能顯示正常。
- Local?字符集設置為其它編碼方式,如?GBK
和上述“沒有設置 Local 字符集”相同,QString::fromLocal8Bit()(此時本地字符集為 GBK)則顯示亂碼,直接輸入、采用 QString::fromUtf8() 都能顯示正常。
(3) 源文件編碼設置為?UTF-8(有?BOM)
- Local?字符集設置為?UTF-8
需要先進行以下設置:
在 main 函數頭文件下面添加以下語句
#if defined(_MSC_VER) && (_MSC_VER >= 1600)
#pragma execution_character_set("utf-8")
#endif
或者在 .pro 文件中添加
QMAKE_CXXFLAGS += /utf-8
設置完成后,直接輸入中文(QString 默認將 char * 當作是 UTF-8 編碼去構造)、采用QString::fromLocal8Bit()(此時本地字符集為 UTF-8)、采用 QString::fromUtf8() 都能顯示正常。
- 沒有設置Local字符集
直接輸入、采用 QString::fromUtf8() 都顯示亂碼,QString::fromLocal8Bit()顯示正常。
如果進行以下設置,則直接輸入、采用 QString::fromUtf8() 都顯示正常,而 QString::fromLocal8Bit() 顯示亂碼。
在 main 函數頭文件下面添加以下語句
#if defined(_MSC_VER) && (_MSC_VER >= 1600)
#pragma execution_character_set("utf-8")
#endif
或者在 .pro 文件中添加
QMAKE_CXXFLAGS += /utf-8
- Local?字符集設置為其它編碼方式,如?GBK
和上述“沒有設置 Local 字符集”相同,直接輸入、采用 QString::fromUtf8() 都顯示亂碼,QString::fromLocal8Bit()?顯示正常。
如果進行以下設置,則直接輸入、采用 QString::fromUtf8() 都顯示正常,而 QString::fromLocal8Bit() 顯示亂碼。
在 main 函數頭文件下面添加以下語句
#if defined(_MSC_VER) && (_MSC_VER >= 1600)
#pragma execution_character_set("utf-8")
#endif
或者在 .pro 文件中添加
QMAKE_CXXFLAGS += /utf-8
情況2:如果是MinGW編譯器
(1) 源文件編碼設置為?GBK
- Local?字符集設置為?GBK
采用 QString::fromLocal8Bit() 對中文進行轉碼,則顯示正常,否則亂碼;因為設置了本地字符集為 GBK 編碼,fromLocal8Bit() 就是將中文轉換為 GBK 編碼。
- 沒有設置?Local?字符集
如果沒有設置 Local 字符集編碼方式,則 Qt 采用默認的系統字符編碼方式 GBK,和上述 “Local 字符集設置為 GBK” 轉碼方法相同。
- Local?字符集設置為其它編碼方式,如?UTF-8
中文顯示亂碼,因為無法將顯示文字的編碼方式設為 GBK。
(2) 源文件編碼設置為?UTF-8(無?BOM)
- Local?字符集設置為?UTF-8
直接輸入中文(QString 默認將 char * 當作是 UTF-8 編碼去構造)、采用 QString::fromLocal8Bit()(此時本地字符集為 UTF-8)、采用 QString::fromUtf8() 都能顯示正常。
- 沒有設置?Local?字符集
如果沒有設置 Local 字符集編碼方式,則 Qt 采用默認的系統字符編碼方式 GBK,QString::fromLocal8Bit()(此時本地字符集為 GBK)則顯示亂碼,直接輸入、采用 QString::fromUtf8() 都能顯示正常。
- Local?字符集設置為其它編碼方式,如?GBK
和上述“沒有設置 Local 字符集”相同,QString::fromLocal8Bit()(此時本地字符集為 GBK)則顯示亂碼,直接輸入、采用 QString::fromUtf8() 都能顯示正常。
(3) 源文件編碼設置為?UTF-8(有?BOM)
UTF-8?有?BOM和無?BOM?對?MinGW?編譯器沒有影響。
- Local?字符集設置為?UTF-8
直接輸入中文(QString 默認將 char * 當作是 UTF-8 編碼去構造)、采用 QString::fromLocal8Bit()(此時本地字符集為 UTF-8)、采用 QString::fromUtf8() 都能顯示正常。
- 沒有設置?Local?字符集
如果沒有設置 Local 字符集編碼方式,則 Qt 采用默認的系統字符編碼方式 GBK,QString::fromLocal8Bit()(此時本地字符集為 GBK)則顯示亂碼,直接輸入(等價于 QString::fromUtf8())、采用 QString::fromUtf8() 都能顯示正常。
- Local?字符集設置為其它編碼方式,如?GBK
和上述“沒有設置 Local 字符集”相同,QString::fromLocal8Bit()(此時本地字符集為 GBK)則顯示亂碼,直接輸入、采用 QString::fromUtf8() 都能顯示正常。
在?Qt5?中使用?QString::QStringLiteral?這個宏來處理中文亂碼問題,這個宏是官方提供專門處理亂碼問題的。