详细了解字符编码,参考:unicode ucs2 utf16 utf8 ansi GBK GB2312-CSDN博客
为了理解编码,我们先来区分 文件中字符编码 和 程序运行时字符编码 的区别。
文件中字符编码 顾名思义 就是 文字保存在文件中的采用的字符编码方式,可以在IDE中看到
程序运行时字符编码,是编译器读取从源文件中读取到字符串后再按要求做的一次字符编码转换后存入所采用的字符编码,这个字符编码转码过程是编译器编译时发生的,经过编译器生成的obj文件和exe中保存的字符串就是采用编码转换后的字符编码,也是运行时的字符编码,这个编码可能与源文件中不是同一种字符编码。
通过编译器参数finput-charset(gcc编译器)/source-charset(vs编译器)设置输入到编译器的源文件的字符编码,通过便器参数fexec-charset(gcc编译器)/execution-charset(vs编译器)设置目标文件中字符编码。
编译出来的程序 进行 文件读写 操作时 需要做额外操作, 特别是读取文件字节流 放入到QString中 时,需要明确知道文件中的字符编码,然后给与正确的转换。
文件编码和输出显示乱码有兴趣可以参考:qt 汉字输出 显示乱码 解决-CSDN博客
QString中只存放unicode的utf16编码的字符串,内部用QChar(short)类型的指针 进行 保存。
如果非要使用utf-8或ansi编码的字符串操作类,可以使用QLatin1String类。也可以考虑使用QByteArray类甚至std::string。
char*变量在内存中存放的字符串默认编码,与编译器参数 execution-charset有关,而vs2015及以下编译器默认为 "/execution-charset=GB2312",也就是char*变量内存中保存时使用ansi(具体为GB2312)编码,vs2022默认为 "/execution-charset=UTF-8",gcc或类gcc编译器默认为 "-fexec-charset=UTF-8",特就是char*使用unicode的utf8编码。可以通过在字符串前加u8强制编译器对某个char*变量在内存中保存时采用unicode的utf8编码。
char*转换成QString,一定会做一次字符编码的转码!!!
通过QString(const char*)构造的QString对象,char*字符串会被QString强制当成unicode的 utf8编码,这是QString代码不可更改的,并隐式的将这个强制当做unicode 的utf8编码的字符串转换成unicode的utf16编码的字符串。vs编译器 的 execution-charset 默认 为ansi编码,存放的编码为ansi编码,如果你qt工程采用vs2015编译器或以下编译器,这时候强制当做unicode的utf8转换成QString,就一定会乱码,(所以这个时候最好设置"/execution-charset=UTF-8"的编译器参数)。QString 官方不建议使用从char*转QString的构造函数。所以在这个构造函数前加了QT_ASCII_CAST_WARN 宏开关和宏提示。
QString中所有的从char*转换到QString的构造函数 或者 由char*隐式转换到QString的函数 或者 参数中含有char*的非static函数 都是隐式调用QString::fromUtf8(char*) 这个静态函数 进行字符编码的转换的。
从QByteArray转QString 与 char*转QString 是一样,也会出现同样的问题
D:\Qt\Qt5.12.0\5.12.0\mingw73_64\include\QtCore\qstring.h
#if !defined(QT_NO_CAST_FROM_ASCII) && !defined(QT_RESTRICTED_CAST_FROM_ASCII)
.......
inline QT_ASCII_CAST_WARN QString(const char *ch)
: d(fromAscii_helper(ch, ch ? int(strlen(ch)) : -1))
{}
inline QT_ASCII_CAST_WARN QString(const QByteArray &a)
: d(fromAscii_helper(a.constData(), qstrnlen(a.constData(), a.size())))
{}
inline QT_ASCII_CAST_WARN QString &operator=(const char *ch)
{ return (*this = fromUtf8(ch)); }
inline QT_ASCII_CAST_WARN QString &operator=(const QByteArray &a)
......
D:\Qt\Qt5.12.0\5.12.0\Src\qtbase\src\corelib\tools\qstring.cpp
......
QString::Data *QString::fromAscii_helper(const char *str, int size)
{
QString s = fromUtf8(str, size);
s.d->ref.ref();
return s.d;
}
......
从char*转到QString ,QString有提供很多的static类型的转码函数,qt建议通过调用这些函数进行显示的编码转换。
D:\Qt\Qt5.12.0\5.12.0\mingw73_64\include\QtCore\qstring.h
static inline QString fromLatin1(const QByteArray &str)//从ascii编码转unicode的utf16编码
{ return str.isNull() ? QString() : fromLatin1(str.data(), qstrnlen(str.constData(), str.size())); }
static inline QString fromUtf8(const QByteArray &str) //从unicode8的编码转换成unicode的utf16编码
{ return str.isNull() ? QString() : fromUtf8(str.data(), qstrnlen(str.constData(), str.size())); }
static inline QString fromLocal8Bit(const QByteArray &str) //从local编码转换虫unicode的utf16编码
{ return str.isNull() ? QString() : fromLocal8Bit(str.data(), qstrnlen(str.constData(), str.size())); }
static QString fromUtf16(const ushort *, int size = -1); //从unicode的utf16编码转unicode的utf16编码,可以在字符串前存放BOM来指定输入的字符串字节序,否则采用系统默认字节序
static QString fromUcs4(const uint *, int size = -1); //从unicode的utf32编码转unicode的utf16编码,可以在字符串前存放BOM来指定输入的字符串字节序,否则采用系统默认字节序
#if defined(Q_COMPILER_UNICODE_STRINGS)
static QString fromUtf16(const char16_t *str, int size = -1)
{ return fromUtf16(reinterpret_cast<const ushort *>(str), size); }
static QString fromUcs4(const char32_t *str, int size = -1)
{ return fromUcs4(reinterpret_cast<const uint *>(str), size); }
#endif
QString::fromUtf8(char*) 转码失败是不会给提示的,但是会将不认识的字节 转成 0xfffd。
如果你将ansi编码的字符串传入, 比如ansi编码的 "你好" 传入,其ansi(GB2312)编码为0xC4E3 0xBAC3 ,会得到由四个0xfffd的QChar组成的QString。
下面的案例中,使用windows下qt5.12+vs2015编译器的场景,采用默认的excution-charset(默认值为GB2312)和source-charset(默认值为GB2312)编译器参数,源文件编码为带BOM的utf8(vs编译器能通过BOM识别到文件为utf8编码,并自动将source-charset设置为utf8编码),QTextCodec为 system编码(在我电脑上也就是ansi(GB2312)),excution-charset为默认的ansi(GB2312)编码。
代码中QString类型的str1和str2都存在隐式地将ansi编码的字符串通过fromutf8() 转变成utf16编码的字符串,utf8并不识别ansi编码的字符串,存在转码错误,且刚好"你好"中的每个字节在utf8中都是非法的,导致char数组变量中的每个字节都变成值为0xfffd的占两个字节的QChar类型数据。 当然,主要问题还是qt中可能存在大量隐式的将char*赋予QString的地方。比如我们常用的qDebug中就有。各种使用char*的地方都可能存在隐式的将chai*转QString而存在字符编码转码的隐患!
测试代码
int main(int argc, char *argv[])
{
char cstr1[]="你好c1";
char cstr2[]=u8"你好c2";
wchar_t cstr3[]=L"你好c3";
QString str1("你好1"); //存在隐式地将gb2312转换成unicode的utf16
QString str2;
str2+="你好2"; //存在隐式地将gb2312转换成unicode的utf16
qDebug()<<"你好"<<endl; //qDebug内部存在隐式的将gb2312转换成unicode的utf16
//显示将gb2312转QString所需的utf16。qt推荐的用法。
QString str3=QString::fromLocal8Bit("你好3");
//显示将Utf8转QString所需的utf16。qt推荐的用法。
QString str4=QString::fromUtf8(u8"你好4");
QString str5=QString::fromLocal8Bit(cstr1);
QString str6=QString::fromUtf8(cstr2);
ushort buffer1[6]={};
ushort buffer2[6]={};
ushort buffer3[6]={};
ushort buffer4[6]={};
memcpy(buffer1,str1.data(),str1.length()*2);
memcpy(buffer2,str2.data(),str2.length()*2);
memcpy(buffer3,str3.data(),str3.length()*2);
memcpy(buffer4,str4.data(),str4.length()*2);
//设置控制台接收ansi(gb2312)编码的字符串。936是windows中GB2312字符编码的代码。
system("chcp 936");
cout<<cstr1<<endl;
qDebug().noquote()<<str1;
qDebug().noquote()<<str2;
qDebug().noquote()<<str3;
qDebug().noquote()<<str4;
qDebug().noquote()<<str5;
qDebug().noquote()<<str6;
return 0;
}
QString中采用utf16编码,unicode字符集中的字符,一个字符需要一到两个utf16编码单元(QChar)来表示,具体来说,在unicode字符集17个平面中的第0个平面的字符用一个字符表示,第0号平面囊括了全世界绝大多数语言最常用的字符,其中包括GB2312的所有字符。
比如在qt中“你”字需要用一个 QChar 或 wchat_t 单元来表示,而" 𬌗" 则需要用两个QChar或wchar_t来表示。QString的size或length描述的是QChar单元的个数而不是指字符数。
fromutf16()和fromutf32()都可以在字符串前面加入BOM,来明确告诉QString,输入的字节流所采用的字节序(大小端)。否则会被默认当做当前系统所采用的字节序存放到QString中。
下面是fromutf16中自动识别输入的字节流中的BOM,并将输入字节流转换成本地字节序的unicode的utf16编码的过程。
D:\Qt\Qt5.12.0\5.12.0\Src\qtbase\src\corelib\tools\qstring.cpp
.......
QString QString::fromUtf16(const ushort *unicode, int size)
{
if (!unicode)
return QString();
if (size < 0) {
size = 0;
while (unicode[size] != 0)
++size;
}
return QUtf16::convertToUnicode((const char *)unicode, size*2, 0);
}
........
D:\Qt\Qt5.12.0\5.12.0\Src\qtbase\src\corelib\codecs\qutfcodec_p.h
.......
enum DataEndianness
{
DetectEndianness,
BigEndianness,
LittleEndianness
};
.......
D:\Qt\Qt5.12.0\5.12.0\msvc2015_64\include\QtCore\qchar.h
......
class Q_CORE_EXPORT QChar {
public:
enum SpecialCharacter {
.......
ByteOrderMark = 0xfeff,
ByteOrderSwapped = 0xfffe,
........
D:\Qt\Qt5.12.0\5.12.0\Src\qtbase\src\corelib\codecs\qutfcodec.cpp
........
QString QUtf16::convertToUnicode(const char *chars, int len, QTextCodec::ConverterState *state, DataEndianness e = DetectEndianness)
{
DataEndianness endian = e;
bool half = false;
uchar buf = 0;
bool headerdone = false;
QChar *qch = (QChar *)result.data();
......
while (len--) {
if (half) {
QChar ch;
if (endian == LittleEndianness) {
ch.setRow(*chars++);
ch.setCell(buf);
} else {
ch.setRow(buf);
ch.setCell(*chars++);
}
if (!headerdone) {
headerdone = true;
if (endian == DetectEndianness) {
if (ch == QChar::ByteOrderSwapped) {
endian = LittleEndianness;
} else if (ch == QChar::ByteOrderMark) {
endian = BigEndianness;
} else {
if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
endian = BigEndianness;
} else {
endian = LittleEndianness;
ch = QChar((ch.unicode() >> 8) | ((ch.unicode() & 0xff) << 8));
}
*qch++ = ch;
}
} else if (ch != QChar::ByteOrderMark) {
*qch++ = ch;
}
} else {
*qch++ = ch;
}
half = false;
} else {
buf = *chars++;
half = true;
}
}
......
QTextCodec主要意义之一就是为QString的toLocal8BIt()和fromLocal8Bit(char*)设置字符编码器。QTextCodec可以通过预编译宏开关QT_NO_TEXTCODEC 进行开启或关闭,默认开启,并会产生一个编码为system的codec,在中文环境的windows下一般为ansi(GB2312)。通过QTextCodec::setCodecForLocale指定codec。
fromLocal8Bit(char*)需要与QTextCodec结合使用,关闭了QTextCodec,输入的字节流会被当做ascii编码进行处理。fromLocal8Bit(char*)需要通过QTextCodec为QString指定字符编码器,而该字符编码要与编译器所采用的编码保持一致,也就是编译器参数 execution-charset(vs编译器参数) /fexec-charset(gcc编译器)所指定的编码保持一致!!!
QString QString::fromLocal8Bit_helper(const char *str, int size)
{
if (!str)
return QString();
if (size == 0 || (!*str && size < 0)) {
QStringDataPtr empty = { Data::allocate(0) };
return QString(empty);
}
#if !defined(QT_NO_TEXTCODEC)
if (size < 0)
size = qstrlen(str);
QTextCodec *codec = QTextCodec::codecForLocale(); //获取到TextCodec设置的字符编码器
if (codec)
return codec->toUnicode(str, size);
#endif // !QT_NO_TEXTCODEC
return fromLatin1(str, size);
}
同样的QString 的 toLocal8BIt() 也依赖QTextCodec,qt输出到控制台窗口时,需要使用QTextCodec进行字符转码,然后再输出到控制台窗口。(如果不指定,会被当成ascii单字节编码去逐个字节处理)具体参考qt 汉字输出 显示乱码 解决-CSDN博客
简单案例:
//这里使用qt+vs2015编译器,并采用默认参数,也就是execution-charset为ansi,文件存储为带BOM的utf8(编译器能通过BOM检测出源文件编码为utf8,并自动设置source-charset为utf8)
#include <QTextCodec>
#include <QDebug>
#include <Windows.h>
int main()
{
//QTextCodec编码器一定要与编译器参数execution-charset(vs编译器,默认为GB2312)/fexec-charset(gcc或类gcc编译器,默认为UTF8)的值一致,
//QTextCodec *codec=QTextCodec::codecForName("GB2312"); //获取 GB2312 的 QTextCodec
QTextCodec *codec=QTextCodec::codecForName("UTF-8"); //获取UTF-8 的 QTextCodec
QTextCodec::setCodecForLocale(codec); //设置QString的fromLocal8Bit() 和toLocal8Bit()的QTextCodec。QDebug,QMessageLogger等qt自定义输出类,输出到控制台窗口时,需要用到codec将QString中的unicode的utf16编码转变为local 8bit编码,没有设置QT_NO_TEXTCODEC宏开关的时候,qt默认有一个编码为system的codec,指定codec和后续设置chcp,让整个输出过程更明确。
QString str=QString::fromUtf8(u8"你好"); //要保证传入fromUtf8的字符串是utf8编码,最简单的是在字符串前面加u8
QByteArray arr = codec->fromUnicode(str);
system("chcp 65001"); //设置控制台输出窗口接收utf8编码的字符串
//system("chcp 936"); //设置控制台输出窗口接收GB2312编码的字符串
cout<<u8"你好"<<endl;
cout<<arr.data()<<endl;
qDebug()<<str<<endl;
qDebug().noquote().nospace()<<str<<endl;
QString s=QString::fromUtf16((ushort*)(L"你好"));
qDebug()<<s<<endl;
return 0;
}