Qt开发中文显示乱码

Qt开发中文显示乱码

来源 https://www.jianshu.com/p/ed269df8104d

参考 https://blog.csdn.net/J_H_C/article/details/93882284

 

为什么会出现乱码

首先,我们需要有的概念是乱码的问题是由编码和解码方式引起的。涉及到编码方式的地方有3个:

  1. 源码字符集
  2. 执行字符集
  3. 运行环境字符集

源码字符集(the source character set):源码文件时使用何种编码格式保存的。
执行字符集(the execution character):可执行程序内保存的是何种编码(程序执行时内存中字符串编码)

gcc 运行字符集设置参数
-finput-charset=charset //设置源码字符集为charset
-fexec-charset=charset //设置执行字符集为charset
-fwide-exec-charset=charset //设置宽字符串的执行字符集为charset

msvc 运行字符集设置参数
-execution-charset:utf-8 // 指明程序执行时使用UTF-8字符集
-source-charset:utf-8 // 指明源码文件的编码为UTF-8字符集

源码字符集确切的说是编译器认为源码文件的编码方式,执行字符集是可执行程序采用的编码方式,而运行环境字符集则是环境支持的编码方式。编译程序处理字符串的过程,实际上是首先读入字符的二进制数,根据编码格式到另一种编码格式转换策略得到另外一串二进制数,所以1->2可能有二进制数的变化,而3则是通过既定的编码方式来解读2中的二进制数为字符(这里为什么说可能呢,因为1和2如果是相同的编码是不需要变化的)。

那么具体是哪些地方引起错误呢?在解答之前先介绍理解该问题的先验知识(由于我的运行环境是window简体中文版,所以以下的locale编码就是指GBK编码):

  • msvc2013编译程序时,处理源码字符集时,有BOM标识符的则正确识别(实际上目前就是有无BOM的utf-8),无BOM则使用本地Locale字符集(随系统设置而变),执行字符集默认用本地Locale字符集(其他msvc版本在看完本文甚至可以根据自己实验猜测处理)。
  • gcc编译程序时,默认两者都是uft-8,有finput-charset源码字符集和fexec-charset执行字符集则按照设置。

那么乱码的原因有:
①编译器解读源码字符集错误。如我是utf-8的源码,因为不带bom你当成locale,执行字符集也是locale所以不需要转换,而本来utf-8到locale是需要转换的。
②源码字符集到执行字符集的转换错误。如本来把识别正确的源码字符集locale转成执行字符集中的utf-8,结果你给我指定了错误了转换方式,说让我通过xxx编码转utf-8的策略转(Note:这是错误的表述,看到下面你就明白,实际上这里的错误只能是应为转换算法的错误)。
③字符解析错误。如果现在程序中的字符串二进制是utf-8的,结果你非要说执行字符集是loacle,那么解析肯定会出错。

还需要理解的包括下面的知识:

  • windows console控制台代码页为locale,即把程序中的字符串二进制表示当locale执行字符集来解读
  • 字符串二进制的表示形式不需要编译,直接拷贝到执行程序的二进制中

乱码情况解析

接下来内容的实例基于csdn作者“在水一方”博文中举的“我是中文”的例子(文末有引用),他的博文在我理解这个问题的本质过程中帮助很大。这里就套用他的例子的,一方面我比较懒,不想举其他例子,另一方面通过验证他的例子,也佐证了我自己的想法。
直接上例子(这里说的都是源码字符集):

const char * str = "我是汉字"
//用GBK编码等价于
const char * str = "\xce\xd2\xca\xc7\xba\xba\xd7\xd6";
//用utf-8编码等价于
const char * str = "\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97";
//note:这里的等价于就是说当计算机到内存中来处理的时候,中文读入的就是等价于的二进制数

翻译一下就是,“我是汉字”这几个字,在GBK编码下就是保存的“\xce\xd2\xca\xc7\xba\xba\xd7\xd6”这样一串二进制,而utf-8则是保存的“\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97”。这里可以使用Notepad++进行验证。

字符解析错误乱码
  • 编译环境:vs2013(msvc2013编译器),源码文件字符集GBK
  • 运行环境:Windows简体中文下的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;

运行程序得到下图结果:

根据结果我们可以看到2是乱码的,而汉字表现出了和GBK下二进制数据一样的结果。有了前面的先验知识按照前面先验的乱码原因①②③来理解:
①对于不带bom源码的文件,msvc2013当成locale处理,而源码字符集恰巧是locale,读入源码字符集没问题。这里需要“我是汉字”字符串变为二进制数,并记录源码字符集。
②源码字符集和执行字符集都是locale,不需要转换,没转换自然转换没问题。到此,字符串的二进制表示的直接拷贝到了执行程序中。
③2把执行程序中“\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97”——“我是中文”uft-8编码下的二进制,当成了GBK编码来解析,所以出现了类型③乱码。
Note:请用notepad++检验,以便理解。

在上面程序的基础上,我们添加测试函数的函数体前添加一段预定义,这是c++11对执行字符集的支持:

//让编译器编译生成程序的执行字符集为utf-8
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif

再次运行程序,得到如下的结果:

首先看到12和上面结果一样,有人这里就有疑问了。你说的字符串的二进制表示直接拷贝我也理解,但是现在我的执行字符集是utf-8啊,那我解读第一个和第二个的结果不应该是这个啊。那你可能忘掉了我之前的一个先验知识了,console不认识utf-8,它仍然会把这串二进制当成locale来解读,所以这里和上面的表现结果是一样的。
下面来看3是怎么回事,①②流程下来:
①源码为locale,编译器也默认认为源码字符集是locale(编译器这是瞎猫碰到死耗子,蒙对了!),解读正确。
②编译器正确知道源码字符集的情况下,需要转化成指定的字符集,自然是会给出正确的转化策略。
最终,编译通过编码转换策略做了一次从 “\xce\xd2\xca\xc7\xba\xba\xd7\xd6”到“\xe6\x88\x91\xe6\x98\xaf\xe6\xb1\x89\xe5\xad\x97”的转换,所以程序中又是“我是中文”uft-8编码下的二进制了,最终又回到了2的情况——类型③乱码。

转换错误乱码(反证)
  • 编译环境:QtCreator(MinGW gcc编译器),源码文件字符集utf-8
  • 运行环境:Windows简体中文下图形界面

下面看一段代码:

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编码,下面代码等价于:
//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();

运行程序得到下图结果:

这里我不给出详细的分析了,通过第1个标签和第2个标签结果都正常,可以验证出gcc编译的默认规则——默认源码字符集和执行字符集都是uft-8,且知道了Qt中QString::fromxxx()函数的作用了。而标签2和标签3的对比可以知道,当环节②出错,就出现乱码了。过程是编译器把读入的utf-8编码下的二进制当成了loacle来解析,这时就解析成了所谓的那串“乱码”,然后正确转换成了uft-8编码下的该“乱码”(Note:这里两次乱码实际的二进制是不一样的哦,只是编码形式不同才有的相同结果,你明白我的意思嘛?)。有人又要疑惑了,不对啊,你明明说这是个类型②的错误,怎么我看着像是类型①乱码呢。其实如果你能这么疑惑,说明你是真的懂了,这里确实是一个类型①的乱码。实际上这里的源码字符集到执行字符集的算法是api内部实现的,所以我们面对这种情况的时候②都不会出问题的。当然了,像你这种乱码都没有理解的人来说,去实现这个算法,那我是不敢用,说不定就会产生类型②乱码了,哈哈。

总结

由于Qt的出现就是为了跨平台,所以QString中统一采用utf-16存储字符串。所有源码中的字符串存放到QString中时,都需要经过一次到utf-16的正确转换。在qt5之前,有两种解决方式解决乱码:

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

相信大家看了前面已经明白这两个函数是意思,这里要提醒一句的就是,两种方式最终在QString中存放的,都是字符串在unicode编码形式下的二进制。

写在最后

这系列的文章将会以自己学习后理解的知识点分享为主,希望吾之所得亦可为汝所得。在2017年3月18日重新更新文章时,我删掉了与知识点无关的表述。只是希望让正努力从“不求甚解”到“先去理解清楚一些以释重负”转变的你,不会因为篇幅过长望而却步。如有疑问,欢迎提问,如有高见,烦请指点。
参考:

 
-----------------------

msvc:QMAKE_CXXFLAGS += -execution-charset:utf-8
msvc:QMAKE_CXXFLAGS += -source-charset:utf-8
msvc:QMAKE_CXXFLAGS_WARN_ON += -wd4819

gcc:QMAKE_CXXFLAGS += -finput-charset=charset:utf-8
gcc:QMAKE_CXXFLAGS += -fexec-charset=charset:utf-8
gcc:QMAKE_CXXFLAGS += -fwide-exec-charset=:utf-8

-----------------------
 
来源  http://blog.csdn.net/aqtata/article/details/9184741
 
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));  
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));  
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));  

网上很多人一碰到编码问题就无脑的Copy上面3行……
从Qt5开始只剩下setCodecForLocale这一个了,只是影响Qt对toLocal8Bit相关函数的编码方式

 
{  
    // Qt默认会使用本机编码,所以对于中文系统,下面这句设置是多余的  
    QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));  
  
    QString str1("你好Hello");  
    QByteArray bLocal = str1.toLocal8Bit(); // 受setCodecForLocale影响,会转换为设定的编码。如果本机不支持指定编码,则会按toLatin1处理  
    QByteArray baLatin1 = str1.toLatin1();  // 不受setCodecForLocale影响,强制转换为ISO-8859-1编码  
    QByteArray bUtf8 = str1.toUtf8();       // 不受setCodecForLocale影响,强制转换为UTF-8编码  
    qDebug() << str1;                       // 正常,Qt会将UTF-16转换为UTF-8输出  
    qDebug() << baLatin1;                   // 乱码,用UTF-8编码输出Latin1字节流  
    qDebug() << bLocal;                     // 乱码,用UTF-8编码输出GBK字节流  
    qDebug() << bUtf8;                      // 正常,用UTF-8编码输出UTF-8字节流  
  
    QString str2 = QString::fromLocal8Bit(bLocal);  
    qDebug() << str2;                       // 正常,因为上面显式指定字节流来自本机编码,而bLocal正是本机编码GBK  
  
    str2 = QString::fromLatin1(bLocal);  
    qDebug() << str2;                       // 乱码,bLocal是GBK编码,但却指定了以Latin1方式去读取,肯定会乱码  
  
    // 字节流来自UTF-8  
    str2 = QString::fromUtf8("\xE4\xBD\xA0\xE5\xA5\xBD\x48\x65\x6C\x6C\x6F");  
    qDebug() << str2;                       // 正常  
  
    // Qt默认采用UTF-8处理字符串,所以不用显式地去调用fromUtf8  
    str2 = QString("\xE4\xBD\xA0\xE5\xA5\xBD\x48\x65\x6C\x6C\x6F");  
    qDebug() << str2;                       // 正常  
  
}  

 

在Qt中,QString会用UTF-16编码存储,而qDebug()等I/O函数会以UTF-8编码处理。

其实转换后的字节流是正确的,只是显示时用了和字节流不同的编码方式处理导致乱码

所以当要在Qt中输入输出非UTF-8字符串时应该先转换一下

 

在源码中要写入非英文字符的话建议使用转义的方式,也就是上面“\xE4\xBD...”这种,这也是官方推荐的方式

为了方便将字符串转换为UTF8转义字符,写了一个小工具

http://download.csdn.net/detail/aqtata/5596247

 

2014-5-6补充:

从vs2010sp1和vs2013开始就已经支持UTF-8的源码文件了,只用在工程里加入一句"#pragma execution_character_set("UTF-8")"即可。不用再做上面的转义了。

 
============= End

 

posted @ 2020-01-07 15:22  lsgxeva  阅读(18281)  评论(0编辑  收藏  举报