徹底解決QT中文碼亂問題
讀完本文,讓你徹底明白Windows下中文亂碼的問題。一勞永逸地解決這個困擾很多同學的問題。
前言
在桌面開發過程中,由于Qt的跨平臺特性,以及更加先進的庫封裝。比起MFC,用著不知道要爽多少。Qt獨創的信號槽機制,也大大方便了開發者。可以讓開發者把更多的精力放在業務的邏輯上,而不是語言和庫的各種細節上。
可是,在使用的過程中,不少朋友在中文Windows系統下,遇到了亂碼的問題。著實頭痛,網上搜了一圈,有時能解決問題,有時不知道什么原因的情況下又出現了奇怪的問題。同樣的問題在cocos2d-x中也會出現。
小伙伴們不要灰心,這個問題連大佬們都頭痛。哈哈~~,請看下面的案例。
上面的問題來自《Cocos2d-x實戰:C++卷》,大佬也很無奈啊。
今天,讓我們來自己剖析一下這個問題。并最終找到一勞永逸的解決方案。
在開始前,我們先來羅列一下遇到的幾種情況:
- 完全正常。(人品大爆發啊)
- 直接亂碼。(哎,時運不濟)
- 編譯報錯——C4819,C2001、C2143。(這是犯了什么天條了嗎?)
- 很小心的使用,可能正常。有時正常,有時編譯報錯,有時末尾的字是亂碼,前面的正常。(這是什么鬼啊)。
細心的小伙伴還總結出了,偶數個中文字符正常,奇數個就不行了。后面再加個英文字符,前面的顯示正常,后面一個字符亂碼。(我也太難了吧~~~)
一、字符編碼
要徹底理解這個問題,我們需要從字符編碼說起,小伙伴們稍微有點耐心,這個其實很容易理解。字符編碼說白了就是一張對照表。
1.1 ASCII編碼
這個編碼很容易,就用了一個字節進行編碼,只能表示英文字符和標點符號。這里我就不過多贅述了,百度一下,就有很多文章有詳細講解。
ASCII編碼表
1.2 中文編碼
計算機剛開始被發明的時候,只有ASCII編碼。也就是說只有英文,那我們怎么辦呢?沒有人幫我們做,那只有自己來了,在1980年,國家標準總局發布了GB2312,其實就是一張中文的編碼對照表。這也不是很復雜的東西,因為單個字節只有256種可能,也就是說,最多只能表示256種字符。那么我們就再多用一個字節唄,在GB2312中,中文就用2個字節進行表示。2^16 = 65536,有這么多種可能,編碼漢字綽綽有余了。
當然,考慮到兼容ASCII編碼,當第一個字符數字小于127時,就表示ASCII字符,用一個字節就夠了。當遇到第一個字符大于127時,就要結合第二個字符來決定是哪個中文字符了。
剛開始GB2312把6000多個中文編了進去,后來發現不夠用,又增加了20000多個字符(包括繁體字),編碼方案名稱改為GBK。再后來,又增加了幾千個少數民族字符,編碼方案名稱改為GB18030。到這里,我們就知道GB2312、GBK、GB18030的編碼方式是一脈相承的。為了后面敘述的方便,我們統稱這種為GBK編碼。
1.3 Unicode編碼
在中國使用GBK編碼方案的同時,其他國家和地區為了使用自己的文字,也紛紛進行對自己的語言文字進行編碼。造成的結果就是,不通用!不同語音的操作系統下編輯的文檔,在另一臺不同語音的計算機中打開就是亂碼。
隨著全球化的發展,急需一種統一的編碼方案,來解決這種混亂的局面。
最終,ISO拿出了Unicode編碼,廢棄所有地區性的編碼方案。重新編碼,所有的字符統一采用2個字節進行存儲。
GBK編碼方案和Unicode編碼完全不同,這也是亂碼的根源。
二、文件編碼
2.1 UTF-8編碼
雖然上文中講到ISO將字符進行了重新編碼,并發布了Unicode。每個字符采用2個字節,16位進行編碼。對于使用英語的國家來說,原來采用的是ASCII編碼,那么所有的文件大小都會變成原來的2倍。這個浪費太大了,于是UTF-8就出現了。
如果用語言描述UTF-8,有些復雜。我們來舉個例子,就很容易明白了。
比如,“中”這個字,Unicode編碼為:0x4E2D。用二進制寫就是(0100-1110-0010-1101),那么用UTF-8,怎么進行表示呢?
1110 0100
1011 1000
1010 1101
我來解釋一下,第一個字節,前面的4位中有連續的3個1,表示這個字符需要有3個字節組成。
第二個字節,前面的2位10,表示上接前面的字節,后面的6位是編碼。
第三個字節同第二個字節,前面的10和后面的編碼。
也就是說,16位的Unicode編碼,被分散到3個字節中。
好麻煩啊……的確,遇到中文或其他多字節編碼的字符是有點麻煩,但是如果是英文字符,直接就用ASCII編碼保存了。直接完全兼容原來的英文文檔,他們就是有這么多的優越性,沒辦法,畢竟計算機技術來自他們那兒。
2.2 ANSI編碼
這又是什么編碼?細心的小伙伴會發現,你在Windows系統上用記事本編輯完文件,點另存為的時候,右下角默認的編碼就是ANSI。這是Windows為了兼容各種不同的編碼,而這樣做的。
其實,他的做法非常簡單,如果遇到小于127的編碼,就是ASCII編碼,計算機都認識這個編碼,對于大于127的編碼,也不用管那么多了,按原樣保存就行了。
三、一勞永逸地消除亂碼
在解決問題前,我們再稍微了解一些背景知識,小伙伴們不要著急啊!
3.1 UTF-8和UTF-8-BOM
UTF-8都夠復雜了,還來個UTF-8-BOM??
其實,不必擔心,這個也是非常簡單的。
讓我們先來看個例子:
上圖,我們在記事本中寫入“中文”,然后,以utf-8保存。
再用notepad++查看存入的內容,以十六進制顯示。這樣沒有問題。
但是,如果再次打開,還會正確顯示嗎?記事本怎么知道我們是按utf-8存儲的呢?如果這個十六進制的串,用GBK解碼就是“涓枃”,是不是有點眼熟啊?我們遇到亂碼的時候,也經常是這種類似的字符。
現在,記事本工作得好好的,但是他有些時候會不會認錯呢?還真會,用記事本新建一個文本文件,輸入“聯通”,保存,再打開。你是不是看到了微軟對聯通滿滿的惡意?哈哈O(∩_∩)O哈哈~
其實,各種軟件在處理文本文件的時候,經常會搞錯!為了解決這種問題,就引入了utf-8-bom。
做法非常簡單,在utf-8文件的開頭加入ef bb bf 三個字節,標示這是一個utf-8的文件,告訴軟件,你可別認錯了。
3.2 編輯器和編譯器對文件的處理
在Qt5 + VS的環境中,編輯器對于我們的源文件解析的完全沒有問題。
可惜的是VS的編譯器卻不是像我們想像的進行工作的。
VS的編譯器經常認錯utf-8文件為ANSI文件,曾經有小伙伴把這個問題,向微軟提交了這個bug,得到的回復如下
The compiler when faced with a source file that does not have a BOM the compiler reads ahead a certain distance into the file to see if it can detect any Unicode characters - it specifically looks for UTF-16 and UTF-16BE - if it doesn't find either then it assumes that it has MBCS. I suspect that in this case that in this case it falls back to MBCS and this is what is causing the problem.
翻譯過來,就是當編譯器遇到不帶BOM的utf-8文件,會讀入一部分進行判斷是否UTF-16和UTF-16BE,如果不是就按照MBCS方式處理。
它根本就不進行utf-8文件的判斷啊,Qt默認保存的就是utf-8文件,并且不帶bom。然后,被按照MBCS方式識別,在我們的環境中,就是按照ANSI方式來處理。
好家伙,,,這么偷懶啊,造成了我們無窮的麻煩……
之前微軟為了這個問題,還出過在文件開頭加上#pragma execution_character_set("utf-8")的方式,后來也被廢棄了。
到這里,我們明白了,Qt默認保存的utf-8文件(不帶bom),被VS的編譯器認成了ANSI格式的文件,就是亂碼的根源。
3.3 Qt中QString對中文的處理
Qt中有QString字符串類,使用非常方便。
經常我們使用2種常用的方式:
QString str1("中文");
QString str2 = QString::fromLocal8Bit("中文");
需要明確的是第一種方法,也就是QString默認構造函數,接受的是utf-8字節序列。第二種方法,接受的是GBK字節序列。
3.4 解決方案
到這里為止,相信大家對怎么解決中文亂碼的方案已經猜出來了。那就是:
在Qt中設置所有保存的文件都是utf-8-bom格式
在需要使用到中文的地方需要使用QString::fromLocal8Bit()方式。
3.5 編譯出錯的問題
到這里,細心的小伙伴就會意識到,雖然,我們亂碼的問題得到了解決,但還是不明白前面4種現象中的后兩種是什么情況。這里,我就再給大家解釋一下。
char * str = "中文中";
看上面的代碼,如果我們保存在utf-8文件中,而編譯器把我們的文件認成了ANSI格式的,也就是中文部分安裝GBK來解析。我們看“中文中”這三個字的utf-8編碼
e4 b8 ad e6 96 87 e4 b8 ad
三個中文字符被編碼成了9個字節,在編譯器按照GBK編碼進行解析,因為GBK編碼中,中文字符需要兩個字節,就把后面的分號就給吞噬掉了。源文件少了個分號,編譯肯定是通不過的。
還剩最后一個問題,如果是
char * str = "中文中 ";
后面多添了一個空格,引號中的utf編碼為:
e4 b8 ad e6 96 87 e4 b8 ad 20
剛好是10個字節,所以,編譯沒有報錯,但是在編譯器編譯的過程中,是按照GBK進行解析的,到解析到最后,遇到ad 20,發現找不到GBK中對應的字符,就把ad 20用3f(?),替代。
QString接收到的字符序列變為:
e4 b8 ad e6 96 87 e4 b8 3f
所以,QString接收到的utf-8序列最后一個字節被改掉了,最后一個字符就顯示出了亂碼了。
其實,文章的開頭提到的第1種沒有問題的情況,很有可能,程序比較簡單,而中文字符出現的個數剛好是偶數。真是人品大爆發,在發生2次誤解的情況下,得到了正確的結果。O(∩_∩)O哈哈~
四、總結
今天,我們介紹了各種字符編碼,文件存儲編碼,VS編譯器,以及QString對字符的處理。總算理順了出現亂碼的原因。最終的原因,就是Qt默認保存為utf-8不帶bom的文件,而VS編譯器對于utf-8文件解析過程中的偷懶,而錯認為ANSI編碼文件所致。Cocos2d-x中的亂碼,相信小伙伴們也已經明白是怎么回事了。