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