Qt開發(fā)中文顯示亂碼

為什么會(huì)出現(xiàn)亂碼

首先痰洒,我們需要有的概念是亂碼的問題是由編碼和解碼方式引起的。涉及到編碼方式的地方有3個(gè):

  1. 源碼字符集
  2. 執(zhí)行字符集
  3. 運(yùn)行環(huán)境字符集

源碼字符集確切的說是編譯器認(rèn)為源碼文件的編碼方式媒佣,執(zhí)行字符集是可執(zhí)行程序采用的編碼方式,而運(yùn)行環(huán)境字符集則是環(huán)境支持的編碼方式。編譯程序處理字符串的過程畏铆,實(shí)際上是首先讀入字符的二進(jìn)制數(shù),根據(jù)編碼格式到另一種編碼格式轉(zhuǎn)換策略得到另外一串二進(jìn)制數(shù)吉殃,所以1->2可能有二進(jìn)制數(shù)的變化,而3則是通過既定的編碼方式來解讀2中的二進(jìn)制數(shù)為字符(這里為什么說可能呢辞居,因?yàn)?和2如果是相同的編碼是不需要變化的)。

那么具體是哪些地方引起錯(cuò)誤呢蛋勺?在解答之前先介紹理解該問題的先驗(yàn)知識(shí)(由于我的運(yùn)行環(huán)境是window簡(jiǎn)體中文版瓦灶,所以以下的locale編碼就是指GBK編碼):

  • msvc2013編譯程序時(shí),處理源碼字符集時(shí)抱完,有BOM標(biāo)識(shí)符的則正確識(shí)別(實(shí)際上目前就是有無BOM的utf-8)贼陶,無BOM則使用本地Locale字符集(隨系統(tǒng)設(shè)置而變),執(zhí)行字符集默認(rèn)用本地Locale字符集(其他msvc版本在看完本文甚至可以根據(jù)自己實(shí)驗(yàn)猜測(cè)處理)巧娱。
  • gcc編譯程序時(shí)碉怔,默認(rèn)兩者都是uft-8,有finput-charset源碼字符集和fexec-charset執(zhí)行字符集則按照設(shè)置禁添。

那么亂碼的原因有:
①編譯器解讀源碼字符集錯(cuò)誤撮胧。如我是utf-8的源碼,因?yàn)椴粠om你當(dāng)成locale老翘,執(zhí)行字符集也是locale所以不需要轉(zhuǎn)換芹啥,而本來utf-8到locale是需要轉(zhuǎn)換的锻离。
②源碼字符集到執(zhí)行字符集的轉(zhuǎn)換錯(cuò)誤。如本來把識(shí)別正確的源碼字符集locale轉(zhuǎn)成執(zhí)行字符集中的utf-8墓怀,結(jié)果你給我指定了錯(cuò)誤了轉(zhuǎn)換方式汽纠,說讓我通過xxx編碼轉(zhuǎn)utf-8的策略轉(zhuǎn)(Note:這是錯(cuò)誤的表述,看到下面你就明白傀履,實(shí)際上這里的錯(cuò)誤只能是應(yīng)為轉(zhuǎn)換算法的錯(cuò)誤)疏虫。
③字符解析錯(cuò)誤。如果現(xiàn)在程序中的字符串二進(jìn)制是utf-8的啤呼,結(jié)果你非要說執(zhí)行字符集是loacle卧秘,那么解析肯定會(huì)出錯(cuò)。

還需要理解的包括下面的知識(shí):

  • windows console控制臺(tái)代碼頁為locale官扣,即把程序中的字符串二進(jìn)制表示當(dāng)locale執(zhí)行字符集來解讀
  • 字符串二進(jìn)制的表示形式不需要編譯翅敌,直接拷貝到執(zhí)行程序的二進(jìn)制中

亂碼情況解析

接下來內(nèi)容的實(shí)例基于csdn作者“在水一方”博文中舉的“我是中文”的例子(文末有引用),他的博文在我理解這個(gè)問題的本質(zhì)過程中幫助很大惕蹄。這里就套用他的例子的蚯涮,一方面我比較懶,不想舉其他例子卖陵,另一方面通過驗(yàn)證他的例子遭顶,也佐證了我自己的想法。
直接上例子(這里說的都是源碼字符集):

const char * str = "我是漢字"
//用GBK編碼等價(jià)于
const char * str = "\xce\xd2\xca\xc7\xba\xba\xd7\xd6";
//用utf-8編碼等價(jià)于
const char * str = "\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97";
//note:這里的等價(jià)于就是說當(dāng)計(jì)算機(jī)到內(nèi)存中來處理的時(shí)候泪蔫,中文讀入的就是等價(jià)于的二進(jìn)制數(shù)

翻譯一下就是棒旗,“我是漢字”這幾個(gè)字,在GBK編碼下就是保存的“\xce\xd2\xca\xc7\xba\xba\xd7\xd6”這樣一串二進(jìn)制撩荣,而utf-8則是保存的“\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97”铣揉。這里可以使用Notepad++進(jìn)行驗(yàn)證。

字符解析錯(cuò)誤亂碼
  • 編譯環(huán)境:vs2013(msvc2013編譯器)餐曹,源碼文件字符集GBK
  • 運(yùn)行環(huán)境:Windows簡(jiǎn)體中文下的Console命令行

下面看一段代碼:

char * cc = "\xce\xd2\xca\xc7\xba\xba\xd7\xd6";
std::cout << cc << std::endl;
char * cc1 = "\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97";
std::cout << cc1 << std::endl;
char * cc2 = "我是漢字";
std::cout << cc2 << std::endl;

運(yùn)行程序得到下圖結(jié)果:

vs2013下未修改執(zhí)行字符集cl編譯運(yùn)行結(jié)果.png

根據(jù)結(jié)果我們可以看到2是亂碼的逛拱,而漢字表現(xiàn)出了和GBK下二進(jìn)制數(shù)據(jù)一樣的結(jié)果。有了前面的先驗(yàn)知識(shí)按照前面先驗(yàn)的亂碼原因①②③來理解:
①對(duì)于不帶bom源碼的文件台猴,msvc2013當(dāng)成locale處理朽合,而源碼字符集恰巧是locale,讀入源碼字符集沒問題饱狂。這里需要“我是漢字”字符串變?yōu)槎M(jìn)制數(shù)曹步,并記錄源碼字符集。
②源碼字符集和執(zhí)行字符集都是locale嗡官,不需要轉(zhuǎn)換箭窜,沒轉(zhuǎn)換自然轉(zhuǎn)換沒問題。到此衍腥,字符串的二進(jìn)制表示的直接拷貝到了執(zhí)行程序中磺樱。
③2把執(zhí)行程序中“\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97”——“我是中文”uft-8編碼下的二進(jìn)制,當(dāng)成了GBK編碼來解析婆咸,所以出現(xiàn)了類型③亂碼竹捉。
Note:請(qǐng)用notepad++檢驗(yàn),以便理解尚骄。

在上面程序的基礎(chǔ)上块差,我們添加測(cè)試函數(shù)的函數(shù)體前添加一段預(yù)定義,這是c++11對(duì)執(zhí)行字符集的支持:

//讓編譯器編譯生成程序的執(zhí)行字符集為utf-8
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif

再次運(yùn)行程序倔丈,得到如下的結(jié)果:

vs2013下執(zhí)行字符集utf-8 cl編譯運(yùn)行結(jié)果.png

首先看到12和上面結(jié)果一樣憨闰,有人這里就有疑問了。你說的字符串的二進(jìn)制表示直接拷貝我也理解需五,但是現(xiàn)在我的執(zhí)行字符集是utf-8啊鹉动,那我解讀第一個(gè)和第二個(gè)的結(jié)果不應(yīng)該是這個(gè)啊。那你可能忘掉了我之前的一個(gè)先驗(yàn)知識(shí)了宏邮,console不認(rèn)識(shí)utf-8泽示,它仍然會(huì)把這串二進(jìn)制當(dāng)成locale來解讀,所以這里和上面的表現(xiàn)結(jié)果是一樣的蜜氨。
下面來看3是怎么回事械筛,①②流程下來:
①源碼為locale,編譯器也默認(rèn)認(rèn)為源碼字符集是locale(編譯器這是瞎貓碰到死耗子飒炎,蒙對(duì)了埋哟!),解讀正確郎汪。
②編譯器正確知道源碼字符集的情況下定欧,需要轉(zhuǎn)化成指定的字符集,自然是會(huì)給出正確的轉(zhuǎn)化策略怒竿。
最終砍鸠,編譯通過編碼轉(zhuǎn)換策略做了一次從 “\xce\xd2\xca\xc7\xba\xba\xd7\xd6”到“\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97”的轉(zhuǎn)換,所以程序中又是“我是中文”uft-8編碼下的二進(jìn)制了耕驰,最終又回到了2的情況——類型③亂碼爷辱。

轉(zhuǎn)換錯(cuò)誤亂碼(反證)
  • 編譯環(huán)境:QtCreator(MinGW gcc編譯器),源碼文件字符集utf-8
  • 運(yùn)行環(huán)境:Windows簡(jiǎn)體中文下圖形界面

下面看一段代碼:

MainWindow w;
QLabel *lb1 = new QLabel(&w);
QLabel *lb2 = new QLabel(&w);
QLabel *lb3 = new QLabel(&w);
lb1->setText(QString("我是漢字"));
lb1->resize(100, 20);
lb1->move(120, 120);
lb2->setText(QString::fromUtf8("我是漢字"));
lb2->resize(100, 20);
lb2->move(120,160);
//由于源碼是utf-8編碼朦肘,下面代碼等價(jià)于:
//lb3->setText(QString::fromLocal8Bit("我是漢字"));
lb3->setText(QString::fromLocal8Bit(
              "\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97"));
lb3->resize(100, 20);
lb3->move(120,200);
w.show();

運(yùn)行程序得到下圖結(jié)果:

qtCreator gcc默認(rèn)字符集設(shè)置編譯運(yùn)行結(jié)果.png

這里我不給出詳細(xì)的分析了饭弓,通過第1個(gè)標(biāo)簽和第2個(gè)標(biāo)簽結(jié)果都正常,可以驗(yàn)證出gcc編譯的默認(rèn)規(guī)則——默認(rèn)源碼字符集和執(zhí)行字符集都是uft-8媒抠,且知道了Qt中QString::fromxxx()函數(shù)的作用了弟断。而標(biāo)簽2和標(biāo)簽3的對(duì)比可以知道,當(dāng)環(huán)節(jié)②出錯(cuò)趴生,就出現(xiàn)亂碼了阀趴。過程是編譯器把讀入的utf-8編碼下的二進(jìn)制當(dāng)成了loacle來解析昏翰,這時(shí)就解析成了所謂的那串“亂碼”,然后正確轉(zhuǎn)換成了uft-8編碼下的該“亂碼”(Note:這里兩次亂碼實(shí)際的二進(jìn)制是不一樣的哦刘急,只是編碼形式不同才有的相同結(jié)果棚菊,你明白我的意思嘛?)叔汁。有人又要疑惑了统求,不對(duì)啊,你明明說這是個(gè)類型②的錯(cuò)誤据块,怎么我看著像是類型①亂碼呢码邻。其實(shí)如果你能這么疑惑,說明你是真的懂了另假,這里確實(shí)是一個(gè)類型①的亂碼像屋。實(shí)際上這里的源碼字符集到執(zhí)行字符集的算法是api內(nèi)部實(shí)現(xiàn)的,所以我們面對(duì)這種情況的時(shí)候②都不會(huì)出問題的浪谴。當(dāng)然了开睡,像你這種亂碼都沒有理解的人來說,去實(shí)現(xiàn)這個(gè)算法苟耻,那我是不敢用篇恒,說不定就會(huì)產(chǎn)生類型②亂碼了,哈哈凶杖。

總結(jié)

由于Qt的出現(xiàn)就是為了跨平臺(tái),所以QString中統(tǒng)一采用utf-16存儲(chǔ)字符串胁艰。所有源碼中的字符串存放到QString中時(shí),都需要經(jīng)過一次到utf-16的正確轉(zhuǎn)換智蝠。在qt5之前腾么,有兩種解決方式解決亂碼:

QString::fromxxx();
QTextCodec::setCodecForxxx();

相信大家看了前面已經(jīng)明白這兩個(gè)函數(shù)是意思,這里要提醒一句的就是杈湾,兩種方式最終在QString中存放的解虱,都是字符串在unicode編碼形式下的二進(jìn)制。

寫在最后

這系列的文章將會(huì)以自己學(xué)習(xí)后理解的知識(shí)點(diǎn)分享為主漆撞,希望吾之所得亦可為汝所得殴泰。在2017年3月18日重新更新文章時(shí),我刪掉了與知識(shí)點(diǎn)無關(guān)的表述浮驳。只是希望讓正努力從“不求甚解”到“先去理解清楚一些以釋重負(fù)”轉(zhuǎn)變的你悍汛,不會(huì)因?yàn)槠^長(zhǎng)望而卻步。如有疑問至会,歡迎提問离咐,如有高見,煩請(qǐng)指點(diǎn)奉件。
參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昆著,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子糖埋,更是在濱河造成了極大的恐慌宣吱,老刑警劉巖窃这,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞳别,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡杭攻,警方通過查閱死者的電腦和手機(jī)祟敛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兆解,“玉大人馆铁,你說我怎么就攤上這事」Γ” “怎么了埠巨?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)现拒。 經(jīng)常有香客問我辣垒,道長(zhǎng),這世上最難降的妖魔是什么印蔬? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任勋桶,我火速辦了婚禮,結(jié)果婚禮上侥猬,老公的妹妹穿的比我還像新娘例驹。我一直安慰自己,他們只是感情好退唠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布鹃锈。 她就那樣靜靜地躺著,像睡著了一般瞧预。 火紅的嫁衣襯著肌膚如雪屎债。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天松蒜,我揣著相機(jī)與錄音扔茅,去河邊找鬼。 笑死秸苗,一個(gè)胖子當(dāng)著我的面吹牛召娜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惊楼,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼玖瘸,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼秸讹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起雅倒,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤璃诀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蔑匣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劣欢,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年裁良,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凿将。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡价脾,死狀恐怖牧抵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侨把,我是刑警寧澤犀变,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站秋柄,受9級(jí)特大地震影響获枝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜华匾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一映琳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜘拉,春花似錦萨西、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至持寄,卻和暖如春源梭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背稍味。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工废麻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人模庐。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓烛愧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怜姿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容