qt 汉字输出 中文输出 显示乱码 qDebug() 乱码 解决

要正确显示汉字,必须要先了解计算机文字编码相关知识,参考:
unicode ucs2 utf16 utf8 ansi GBK GB2312 互转 及 渲染_ucs2编码转换-CSDN博客
qt vs 编程 字符编码 程序从源码到编译到显示过程中存在的字符编码及隐藏的字符编码转换 中文输出 乱码 原因-CSDN博客

1、汉字输出到 应用程序输出面板

qt 自定义的输出类qDebug() 、QDebug对象、QMessageLogger默认输出到 应用程序输出面板 。

这些输出工具类对象会将所有的字符串转变为QString类型,然后内部代码会利用Windows.h中OutputDebugStringW(wchar_t*) 输出到面板上。当使用vs编译器时,你也可以直接调用这个windows API输出到面板上。

我们都知道QString类型中将字符串全部按unicode的utf16编码,每个字符都有两个字节保存(或四个字节)。正常来将,存放字符串到QString中时指定正确的编码,就能正确的显示字符串。

复制代码
#include <QDebug>
#include <Windows.h>

int main()
{
    QString str1 = QString::fromWCharArray(L"你好"); // 字符串前加L,强制将字符串保存为unicode utf16编码保存到内存中
    QString str2 = QString::fromUtf8(u8"你好"); //字符串前加u8,强制将字符串保存为unicode utf8编码保存到内存中。
    qDebug()<<"str1:"<<str1<<endl<<"str2:"<<str2<<endl;

    OutputDebugString(L"你好");
    return 0;
}

如果不加前缀,就需要你对文件字符编码和编译器默认字符编码及要求有所了解, 可以参考这个:程序从源码到编译到显示过程中存在的字符编码及隐藏的字符编码转换

但有个意外:当qt使用mingw编译器 时,输出到应用程序输出面板 上时,给OutputDebugStringW()投喂unicode的utf-16编码(双字节)的字符串,会被先莫名其妙的转换为GBK,然后被莫名其妙的按字节扩张,把单字节变成双字节。比如"你好" ,utf16为0x4F60 0x597D,虽然内存中存放的还是正确的utf16编码,但是在选用Mingw编译器输出到output panel(qt creator 4.8)时显示成ÄãºÃ,对应的utf16编码为:0x00C4 0x00E3 0x00BA 0x00C3

而使用OutputDebugStringA()时,传入utf8编码却能正确显示,如果直接手动传ansi编码字符反而是错误的(不管exec-charset设置成什么都是如此),而window API OutputDebugStringA表示要传入的是ansi字符编码。这说明Mingw在编译时额外在调用Windows API接口的地方插入了字符转码的操作(没错,Mingw不是直接修改内存中的字符编码, 而是插入转码的代码,这其实是多余的操作,它目的可能是方便调用A后缀windows系统函数时将mingw exec-charset默认的utf8所内存编码隐式转换成ansi编码后再调用windows下的相应接口,以便支持linux下的程序无障碍在windows下编译通过。但是调用W后缀的windows系统函数时的转码是错误的)。

目前的结论:

1、在windows下是Mingw的exec-charset只能设置为utf8,在调用带A后缀的windows API时,不需要传入ansi编码字符串,而只需要传入utf8编码字符串。

2、MingW不支持windows的W后缀函数的调用。

下面是两个可以直接显示到output panel 的windows api。加上<windows.h>就可以直接调用

复制代码
    OutputDebugStringW(L"aaa你好"); //windows api,用于将utf16编码的字符串显示到output panel上
    OutputDebugStringA("aaa你好");  //windows api,用于将ansi编码的字符串显示到output panel上    

2、汉字输出到控制台窗口

qt自定义输出类QDebug类、qDebug()、QMessageLogger类、qt_message_output() 默认输出到到qt ide 的 应用程序输出面板 (application output panel)上,需要通过设置来输出到控制台窗口

vs qt 调试 输出 打印 到 输出窗口 或 控制台窗口_qt输出信息到窗口-CSDN博客

控制台窗口显示正确的汉字需要很小心。

1、明确要操作的字符串在内存中是什么编码。如果只是做简单测试,在字符串前用u8或L前缀修饰,先明确拿到的字符串编码,避免编译器对字符串的隐式转码。比如:vs 编译器对字符串编码的隐式转码

2、避免QString的错误转码。字符串直接传给QString的构造函数,QString内部都会隐式的调用fromUtf8() 将字符串自动转码成utf16编码(fromlocal),如果你传入QString构造函数的是ansi编码字符串,那么这个时候就会出现转码错误。要将字符串正确传送给QString,最好是显示调用对应的fromxxxx函数进行正确的字符编码转换。如果需要保持原来的字符编码,可以使用QByteArray 或者QLatin1String进行保存。这个隐式字符编码转码是真的很隐蔽,因为QString的构造都被包装起来了。char*、QByteArray、QLatin1String在输出的时候基本都会存在如下的隐式转码,内部通过通过fromutf8转成QString再输出。所以要输出的时候还是要调用fromxxxx显示的转成QString再输出。

复制代码
//这里存在隐式字符编码转码,下面是qDebug的源码实现,可以发现调用了QString::fromUtf8
qDebug()<<"str"<<endl;    

......................
//D:\Qt\5.15.2\Src\qtbase\src\corelib\io\qdebug.h
inline QDebug &operator<<(const char* t) { stream->ts << QString::fromUtf8(t);

3、要明确知道你投喂给控制台窗口的字符串是什么编码的。QString中字符串是以unicode的utf16编码进行保存的。当需要输出到控制台的时候,内部会通过QTextCodec进行转码,转成QTextCodec所设置的目标编码的字符串。然后再输出给控制台。当然QTextCodec默认是不需要你做操作的,但是如果能明确那是最好的了 。QDebug将QString向控制台输出的时候,需要调用到QString的toLocal8Bit()(需要调试到源码中才能看到),QTextCodec类的唯一功能就是在这个函数中进行字符编码转码。这是影响在控制台显示是否乱码的一个因素。如果使用qt自定义输出类及接口,想要正确地向控制台窗口输出汉字,必须要指定正确的QTextCodec,使其与控制台使用的字符编码一致。QTextCodec默认为system,windows下为ansi(GBK),linux下为utf8。QTextCodec::setCodecForLocale后,QTextCodec会作用于所有QString的toLocal8BIt和fromLocal8Bit两个成员函数。

QString 与 字符编码 QTextCodec-CSDN博客

4、控制台只支持显示 兼容ascii编码 的编码方式, 可以是ansi(GBK),可以是utf8,但是不支持显示utf16编码。所以QString向控制台输出的时候一定有一个tolocal8bit()的过程。
控制台所支持的字体库有限,并且只能两个字节字符集,比如GBK字符集(字符集与字符编码是两个概念不要搞混),对于超出的字符集范围的字符会显示乱码或两问号 。具体原因比较复杂。

通过system("chcp 65001"); 设置控制台显示utf8编码的字符;通过system("chcp 936"),设置控制台显示GBK编码的字符。

5、直接用C++或C语言的标准输出方式向控制台进行输出。对于QString,获取到data之后直接将其当做wchat_t类型数据来用。

下面是QStrnig::tolocal8bit()的部分源码。

复制代码
D:\Qt\Qt5.12.0\5.12.0\Src\qtbase\src\corelib\global\qlogging.cpp
......
static void stderr_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
    QString formattedMessage = qFormatLogMessage(type, context, message);

    // print nothing if message pattern didn't apply / was empty.
    // (still print empty lines, e.g. because message itself was empty)
    if (formattedMessage.isNull())
        return;
//QDebug内部实现中通过标准输出stderr 输出到控制台
    fprintf(stderr, "%s\n", formattedMessage.toLocal8Bit().constData()); 
    fflush(stderr);
}
......

D:\Qt\Qt5.12.0\5.12.0\Src\qtbase\src\corelib\tools\qstring.cpp
......
QByteArray QStringRef::toLocal8Bit() const
{
    return qt_convert_to_local_8bit(*this);
}
.....
static QByteArray qt_convert_to_local_8bit(QStringView string)
{
    if (string.isNull())
        return QByteArray();
#ifndef QT_NO_TEXTCODEC
    QTextCodec *localeCodec = QTextCodec::codecForLocale();
    if (localeCodec)
        return localeCodec->fromUnicode(string);
#endif // QT_NO_TEXTCODEC
    return qt_convert_to_latin1(string); //被当做ascii编码处理。
}
......

案例

复制代码
#include <QTextCodec>
#include <QDebug>
#include <Windows.h>
int main()
{
    //QTextCodec编码器要与编译器参数execution-charset(vs编译器,默认为GBK)/fexec-charset(gcc或类gcc编译器,默认为UTF8)的值一致,
    QTextCodec *codec=QTextCodec::codecForName("GBK");   //设置QString的fromLocal8Bit() 和toLocal8Bit()的QTextCodec为GBKQTextCodec
    //QTextCodec *codec=QTextCodec::codecForName("UTF-8");  //设置QString的fromLocal8Bit() 和toLocal8Bit()的QTextCodec为UTF-8 QTextCodec
    QTextCodec::setCodecForLocale(codec);
 
    QString s=QString::fromLocal8Bit("你好");
    //QString s=QString::fromUtf8(u8"你好");  //
    system("chcp 936");      //设置控制台输出窗口接收GBK编码的字符串
    //system("chcp 65001");  //设置控制台输出窗口接收utf8编码的字符串
    qDebug()<<s<<endl;
    return 0;
}

3、编译器坑

vs2015编译器,需要更新 Visual Studio 2015 Update 2,才能支持下面两个选项。

下面两个参数告知编译器输入源文件编码 及 告诉编译器编译程序时将字符串保存到内存中使用的字符编码(使用u8和L前缀字符串除外),这样通过变量访问或者调试查看内存,会发现获取到的字符编码就是execution-charset所指定的字符编码。

复制代码
//gcc 或 类gcc编译器
QMAKE_CXXFLAGS += -finput-charset=UTF-8
QMAKE_CXXFLAGS += -fexec-charset=UTF-8


#QMAKE_CXXFLAGS += -fwide-exec-charset=UTF-16  
#设置wchar_t类型数据的编码格式。不同主机值可能不同,编译器运行时根据主机情况会自动识别出最符合
#主机的方案作为默认值,这个参数是不需要动的。UTF-16 UTF-16BE UTF-16LE UTF-32LE UTF-32BE

//vs编译器
QMAKE_CXXFLAGS += /source-charset:utf-8
QMAKE_CXXFLAGS += /execution-charset:utf-8

上面的参数不能混用

复制代码
//测试代码
#include <QDebug>
#include <QTextCodec>
#include <iostream>
using namespace std;


int main(int argc, char *argv[])
{
    QTextCodec *codec = QTextCodec::codecForName("UTF-8");  //
    QTextCodec::setCodecForLocale(codec);
    //cout<<::getenv("path")<<endl;
    system("chcp 65001");
    cout<<u8"你好𬌗"<<endl;
    qDebug()<<"aa你好𬌗"<<endl;
    return 1;
}

测试结果

QString 与 字符编码 QTextCodec-CSDN博客
qt 日志输出 QMessageLogger QDebug QLoggingCategory qDebug qt_message_output() qInstallMessageHandler()-CSDN博客

相关推荐
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner1 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz6 天前
QML Hello World 入门示例
qt
xcyxiner9 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner10 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner10 天前
DicomViewer (添加模型类)3
qt
xcyxiner11 天前
DicomViewer (目录调整) 2
qt
xcyxiner11 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能13 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G13 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt