• 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 = "xcexd2xcaxc7xbaxbaxd7xd6";
    //用utf-8编码等价于
    const char * str = "xe6x88x91xe6x98xafxe6xb1x89xe5xadx97";
    //note:这里的等价于就是说当计算机到内存中来处理的时候,中文读入的就是等价于的二进制数
    

    翻译一下就是,“我是汉字”这几个字,在GBK编码下就是保存的“xcexd2xcaxc7xbaxbaxd7xd6”这样一串二进制,而utf-8则是保存的“xe6x88x91xe6x98xafxe6xb1x89xe5xadx97”。这里可以使用Notepad++进行验证。

    字符解析错误乱码
    • 编译环境:vs2013(msvc2013编译器),源码文件字符集GBK
    • 运行环境:Windows简体中文下的Console命令行

    下面看一段代码:

    char * cc = "xcexd2xcaxc7xbaxbaxd7xd6";
    std::cout << cc << std::endl;
    char * cc1 = "xe6x88x91xe6x98xafxe6xb1x89xe5xadx97";
    std::cout << cc1 << std::endl;
    char * cc2 = "我是汉字";
    std::cout << cc2 << std::endl;
    

    运行程序得到下图结果:

    根据结果我们可以看到2是乱码的,而汉字表现出了和GBK下二进制数据一样的结果。有了前面的先验知识按照前面先验的乱码原因①②③来理解:
    ①对于不带bom源码的文件,msvc2013当成locale处理,而源码字符集恰巧是locale,读入源码字符集没问题。这里需要“我是汉字”字符串变为二进制数,并记录源码字符集。
    ②源码字符集和执行字符集都是locale,不需要转换,没转换自然转换没问题。到此,字符串的二进制表示的直接拷贝到了执行程序中。
    ③2把执行程序中“xe6x88x91xe6x98xafxe6xb1x89xe5xadx97”——“我是中文”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(编译器这是瞎猫碰到死耗子,蒙对了!),解读正确。
    ②编译器正确知道源码字符集的情况下,需要转化成指定的字符集,自然是会给出正确的转化策略。
    最终,编译通过编码转换策略做了一次从 “xcexd2xcaxc7xbaxbaxd7xd6”到“xe6x88x91xe6x98xafxe6xb1x89xe5xadx97”的转换,所以程序中又是“我是中文”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(
                  "xe6x88x91xe6x98xafxe6xb1x89xe5xadx97"));
    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("xE4xBDxA0xE5xA5xBDx48x65x6Cx6Cx6F");  
        qDebug() << str2;                       // 正常  
      
        // Qt默认采用UTF-8处理字符串,所以不用显式地去调用fromUtf8  
        str2 = QString("xE4xBDxA0xE5xA5xBDx48x65x6Cx6Cx6F");  
        qDebug() << str2;                       // 正常  
      
    }  

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

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

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

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

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

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

    2014-5-6补充:

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

     
    ============= End
  • 相关阅读:
    正则表达式(二):Unicode诸问题(上)
    ANT Notes
    Linux下OpenGL开发 -- 准备篇 (转)
    两个和尚
    Office 2008 for Mac 安装笔记
    从软件工程师到IT猎头:我的一点经历和感触 (转)
    One splitpath implementation (platform independent)
    ANT的使用(转)
    80后中专毕业奋斗10年 我的理财选择
    用ANT来实现邮件发送
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/12161743.html
Copyright © 2020-2023  润新知