第十课:Qt 字符编码和中文乱码相关问题

功能描述:最全的 Qt 字符编码相关知识以及中文乱码的原因与解决办法

一、字符编码种类

ASCII 

美国人对信息交流的编码,包括 26 个字母(大小写)、数字和标点符号等,用一个字节(8 位)表示这些字符,实际只编到了第 127 个,这就是 ASCII (American Standard Code for Information Interchange) 编码。

ASCII 扩展码

计算机传到其他国家,发现有些本国的字符在 ASCII 码中没有,就将这些字符编到 ASCII 剩余的位置(128 到 255),这就是 ASCII 扩展码。

gb2312

中文常用的汉字就 6000 多个,中国人采用 gb2312 编码,使用两个字节(16 位),有 65536 个编码位。前 127 个和 ASCII 一样,后面的是汉字的编码。原先的 ASCII 就是半角符号,后来新编的就是全角符号。“英文占一个字节,汉字占两个字节”,说的就是这套编码。

gbk

gb2312 + 生僻字 = gbk

gb10830

gbk + 少数民族文字 = gb10830

Unicode

国际标准化组织制定出一套包含全世界所有语言文字的编码,大家都用这套编码,免得乱七八糟的,谁也不认识谁,这套编码就是 Unicode。

utf

Unicode 只是一套标准,它只是规定了数字多少对应哪个字符,但它不是计算机内部可用的具体实现,就需要有给具体的方案,将数字和字符的对照表输入到计算机内让机器也认识,utf 就是 Unicode Transformation Format 的缩写,意为 Unicode 转换格式。

utf24

Unicode 最长的编码需要 24 位,用 3 个字节表示一个字符,这个方案就被称为 utf24。这个想法好是好,但是不符合实际情况。计算机一次读取的字节只能是 1、2、4、8 这样子,不能一次读 3个字节,所以 24 位的方案很快就被放弃了。

utf32

用 4 个字节表示一个字符,这个方案就被称为 utf32。优点是简单明确,不管是什么字符,全都按 4 位读取,简单明了。但缺点是浪费空间,本来最多 3 个字节就够了,但这每个字符至少浪费了一个字节。像英文这种一个字节就够,最低位是具体内容,其余 3 位都是 0,直接有 3 位是浪费的,也就是说一个 1GB 的英文文档,需要 4GB 的存储空间,生生的浪费的 3 倍。

utf16

有人提出用 16 位就够了,那些一个字节的两个字节的字符直接用,三个字节的通过某种算法可以使用两个字节来实现。使用 16 位来实现 Unicode 的码表,这套方案就是 utf16。

utf8

utf8 是一个变长方案,对于英文,只要一个字节就行,汉字则需要 3 个字节。

二、char * 和 QString 编码问题

char * 类型字符串是什么编码?

对于程序内部声明的字符串,char * 的编码取决于程序源文件的编码,源文件是什么编码,字符串就是什么编码。

对于程序外部传入的字符串,比如从 argv 中读取的字符串,其编码与源文件的编码无关,只看当前操作系统的编码。

QString 是什么编码?

Qt 的文档中明确指出,QString 内部使用的是 Unicode 编码,具体实现方案是 utf16。在使用char * 构造 QString 实例的时候,QString 默认将 char * 当作是 utf8 编码去构造,即:QString s(char *) 等价于 QString s = QString::fromUtf8(char*)。

同样是使用 char * 构造 QString,为什么有的时候是乱码,有的时候就不是乱码?这取决于你的char * 是什么编码了,如果是 utf8 的,就不会有乱码问题,如果是 gbk 的,就会出现乱码的情况。

三、字符串长度

QString 字符串长度

只要 QString 能够正常构造,没有乱码,不管是英文还是中文,都占一个长度。别忘了,QString 用的是 utf16 方案,一个长度占两个字节。

char * 字符串长度

char * 的长度需要看具体的编码了,gbk 是英文一个长度,汉字两个长度,这里一个长度占一个字节。utf8 是英文一个长度,汉字三个长度,这里的一个长度也是一个字节。

所以,不要太相信你的 strlen 函数,关键是得看你是什么编码。

四、QTextCodec::setCodecForLocale 的真正含义

这个函数真正的作用只是告诉你的程序,当前程序内部使用的字符编码是什么。这个函数无法对外部传入和内部声明的字符串产生影响,和系统的编码或者源文件的编码无关。

经测试,这个函数只对 QString 的 Local8bit 函数有影响。如果用这个函数将程序编码设置为 gbk,那么 Local8bit 就是 gbk,如果设置的编码是 utf8,Local8bit 就是 utf8。用了这个函数解决了中文乱码问题一定是因为使用了 QString 的 toLocal8bit 或者 fromLocal8bit,否则这个函数不会对中文乱码问题有任何影响。

假设你的系统是 gbk 的编码,进程默认使用的编码也是 gbk,这时候拿到一个 utf8 的 char *,然后用 fromLocal8bit 来构造 QString,这时候的 Local8bit 是 gbk,但 char 是 utf8,用 gbk 的规则去构造 utf8 的编码就乱码了。

五、char * 转 QString

utf8 的 char * 转 QString

如果 char *是 utf8 的,QString 默认将 char * 当 utf8 处理,用 QString s(char *) 或者 QString s = QString::fromUtf8(char*) 直接构造就行了。

gbk 的 char * 转 QString

如果 char * 是 gbk 的,需要先用 QTextCodec::setCodecForLocale 将 Local8bit 设置成 gbk,再使用 fromLocal8bit 来构造。

QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));

QString s = QString::fromLocal8bit(char*);

不确定编码的 char * 转 QString

在实际编程的时候,经常不确定这个 char * 是 utf8 的还是 gbk 的,这时候可以用下面的方法进行构造。这段代码的含义是,先将 char * 转成 utf8,看是否成功,不成功再转成 gbk。

char * c = "abcdefg";
QTextCodec::ConverterState state;
QString s = QTextCodec::codecForName("UTF-8")->toUnicode(c, strlen(c), &state);
if(state.invalidChars > 0)
{
    s = QTextCodec::codecForName( "GBK" )->toUnicode(c);
}

 在 Qt5 中使用 QString::QStringLiteral 这个宏来处理中文乱码问题,这个宏是官方提供专门处理乱码问题的。

tr() 不是处理中文乱码的,这个函数是用来处理多语言问题的

六、Windows 系统本地字符集编码

在 windows 命令行终端模式下,输入命令:chcp

如上图,我的活动代码页为 936,意思是“中国-简体中文 (GB2312)"

下表列出了所有支持的代码页及其国家(地区)或者语言:

代码页国家(地区)或语言
437美国
708阿拉伯文(ASMO 708)
720阿拉伯文(DOS)
850多语言(拉丁文 I)
852中欧(DOS)-斯拉夫语(拉丁文 II)
855西里尔文(俄语)
857土耳其语
860葡萄牙语
861冰岛语
862希伯来文(DOS)
863加拿大-法语
865日耳曼语
866俄语-西里尔文(DOS)
869现代希腊语
874泰文(Windows)
932日文(Shift-JIS)
936中国-简体中文(GB2312)
949韩文
950繁体中文(Big5)
1200Unicode
1201Unicode (Big-Endian)
1250中欧(Windows)
1251西里尔文(Windows)
1252西欧(Windows)
1253希腊文(Windows)
1254土耳其文(Windows)
1255希伯来文(Windows)
1256阿拉伯文(Windows)
1257波罗的海文(Windows)
1258越南文(Windows)
20866西里尔文(KOI8-R)
21866西里尔文(KOI8-U)
28592中欧(ISO)
28593拉丁文 3 (ISO)
28594波罗的海文(ISO)
28595西里尔文(ISO)
28596阿拉伯文(ISO)
28597希腊文(ISO)
28598希伯来文(ISO-Visual)
38598希伯来文(ISO-Logical)
50000用户定义的
50001自动选择
50220日文(JIS)
50221日文(JIS-允许一个字节的片假名)
50222日文(JIS-允许一个字节的片假名-SO/SI)
50225韩文(ISO)
50932日文(自动选择)
50949韩文(自动选择)
51932日文(EUC)
51949韩文(EUC)
52936简体中文(HZ)
65000Unicode (UTF-7)
65001Unicode (UTF-8)

.cpp 或 .h 文件从 window 上传到 Ubuntu 后会显示乱码,原因是因为 ubuntu 环境设置默认是 utf-8,Windows 系统本地字符集默认编码为 GBK

在简体中文 windows 系统下,ANSI编码代表 GBK/GB2312 编码

七、中文乱码现象

如果在 Qt Creator 中无法输入中文,则选择“编辑”>Select Encoding…>GBK…”或“UTF8+BOM”,点击“按编码重新载入”,此时就可以输入中文了。

列举最常用的 3 个编译器(微软 VC++ 的 cl 编译器MinGW 中的 g++Linux 下的 g++),源代码分别采用 GBK 和无 BOM 的 UTF-8 以及有 BOM 的 UTF-8 这 3 种编码进行保存,发生的现象如下表所示:       

★★★★★

情况1:指的是 Local 字符集为 GBK

情况2:指的是 Local 字符集为 UTF-8

源代码的编码编译器显示正常显示乱码
GBKwin vs cl情况1情况2
win MinGW-g++情况1情况2
linux g++情况1情况2
UTF-8(无 BOM)win vs cl

编译失败

error C2001:常量中有换行符

编译失败

error C2001:常量中有换行符

win MinGW-g++情况2情况1
linux g++情况2情况1
UTF-8(有 BOM)win vs cl

情况1

情况2(有 #pragma 预处理)

情况2(有 #pragma 预处理)
win MinGW-g++情况2情况1
linux g++情况2情况1

源代码的编码:“菜单” -> “工具” -> “选项” -> “文本编辑器” -> “行为” -> “文件编码” -> “默认编码”

常用的选项有以下几个:

System (简体中文 windows 系统默认指的是 GBK 编码)

GBK/windows-936-2000/CP936/MS936/windows-936

UTF-8

Local 字符集:取决于 QTextCodec * codec = QTextCodec::codecForName();

当使用 Visual C++ 编译程序的时候,它会分析源文件采用何种编码,有 BOM 标识符则可以正确识别其编码是 UTF-8,若没有 BOM 标识符则认为其使用本地字符集编码(Local 字符集)。

如果源文件是 UTF-8+BOM 的编码方式,还需要在头文件加入:

#if defined(_MSC_VER) && (_MSC_VER >= 1600)
#pragma execution_character_set("utf-8")
#endif

或者在 .pro 文件中添加:

QMAKE_CXXFLAGS += /utf-8

如果源文件是 UTF-8+无 BOM 的编码方式,则一定不能加 #pragma execution_character_set(“utf-8”),不然会产生乱码。

当 QTextCodec::codecForName("utf-8") 时:

QString::fromLocal8Bit 和 QString::fromUtf8 是等效的。

当 QTextCodec::codecForName("gbk") 时:

QString::fromLocal8Bit 和 QString::fromUtf8 是不等效的。

★★★★★

如果该工程不需要跨平台使用(只在 Win),那么工程设置请使用 GBK 的编码方式;

如果该工程要跨平台使用(Win+Linux),那么工程设置请使用 UTF-8+BOM 的编码方式,Local 字符集设置为 UTF-8

UTF-8 和 GBK 其实对英文和数字都是一样的 ASCII 单字节编码,所以源文件用英文和数字是肯定不乱码,主要是汉字之类的本地语言文字编码显示容易出错。

Windows 系统里一般的记事本、编辑器、VC++ 开发环境等都是默认用 GBK 汉字编码,而  Linux 和 Qt 都是默认用 UTF-8 国际文字编码,所以文本显示乱码一般都是这个原因。

八、“UTF-8 无 BOM 格式”和 “UTF-8 带 BOM 格式”的区别

BOM—Byte Order Mark,就是字节序标记

UTF-8 带 BOM 格式,就是在文件头添加了 3 个 bits 的 b'\xef\xbb\xbf'字符

通常编程,特别是 Linux 下编程建议使用 “UTF-8 无 BOM 格式”,这种不含 BOM 的 UTF-8 才是标准形式,由于含有 BOM 的 UTF-8 常常和 Linux 系统经常使用的 #! 冲突。

若是在 windows 下编程,建议使用 “UTF-8 带 BOM 格式”,这样比较好。

如今只有微软还在坚持使用带 BOM 格式的 UTF-8,由于它便于较快的与不少本地编码,如 gbk,ascii 相区分。总之,微软为了向前兼容性,一直坚持使用带 BOM 格式的 UTF-8。

九、查看 Qt Creator 源文件的编码格式

点击菜单 “工具” -> “选项” -> “文本编辑器”,右边选择“显示”,看到下图:

选中 “Display file encoding”,然后点击 “OK” 按钮,就可以在编辑器右上角看到当前文件的编码格式:

十、解决中文乱码的方法

总结下,解决中文乱码的方法:

其中,源代码的编码:“菜单” -> “工具” -> “选项” -> “文本编辑器” -> “行为” -> “文件编码” -> “默认编码”;Local字符集:取决于QTextCodec *codec = QTextCodec::codecForName();

必须保证源文件的编码和显示文字的编码保持一致,否则中文乱码

放了方便起见,通常将源文件的编码和显示文字的编码设置保持一致。

如果没有设置 Local 字符集,则使用默认系统的字符编码方式,windows 下是 GBK 编码,Linux 下是 UTF-8 编码。

直接输入文字,QString 默认将 char * 当作是 UTF-8 编码去构造。

情况1:如果是msvc 2013/2015/2017等编译器

(1) 源文件编码设置为 GBK

  • Local字符集设置为 GBK

采用 QString::fromLocal8Bit() 对中文进行转码,则显示正常,否则乱码;因为设置了本地字符集为 GBK 编码,fromLocal8Bit() 就是将中文转换为 GBK 编码。

  • 没有设置 Local 字符集

如果没有设置 Local 字符集编码方式,则 Qt 采用默认的系统字符编码方式 GBK,和上述 “Local字符集设置为GBK” 转码方法相同。

  • Local 字符集设置为其它编码方式,如 UTF-8

中文显示乱码,因为无法将显示文字的编码方式设为 GBK。

(2) 源文件编码设置为 UTF-8(无 BOM)

大概率报错:编译失败,error C2001: 常量中有换行符

有时候能显示出来,如果没报错:

  • Local 字符集设置为 UTF-8

直接输入中文(QString 默认将 char * 当作是 UTF-8 编码去构造)、采用 QString::fromLocal8Bit()(此时本地字符集为 UTF-8)、采用 QString::fromUtf8() 都能显示正常。

  • 没有设置 Local 字符集

如果没有设置 Local 字符集编码方式,则 Qt 采用默认的系统字符编码方式 GBK,QString::fromLocal8Bit()(此时本地字符集为 GBK)则显示乱码,直接输入、采用QString::fromUtf8() 都能显示正常。

  • Local 字符集设置为其它编码方式,如 GBK

和上述“没有设置 Local 字符集”相同,QString::fromLocal8Bit()(此时本地字符集为 GBK)则显示乱码,直接输入、采用 QString::fromUtf8() 都能显示正常。

(3) 源文件编码设置为 UTF-8(有 BOM)

  • Local 字符集设置为 UTF-8

需要先进行以下设置:

在 main 函数头文件下面添加以下语句

#if defined(_MSC_VER) && (_MSC_VER >= 1600)
#pragma execution_character_set("utf-8")
#endif

或者在 .pro 文件中添加

QMAKE_CXXFLAGS += /utf-8

设置完成后,直接输入中文(QString 默认将 char * 当作是 UTF-8 编码去构造)、采用QString::fromLocal8Bit()(此时本地字符集为 UTF-8)、采用 QString::fromUtf8() 都能显示正常。

  • 没有设置Local字符集

直接输入、采用 QString::fromUtf8() 都显示乱码,QString::fromLocal8Bit()显示正常

如果进行以下设置,则直接输入、采用 QString::fromUtf8() 都显示正常,而 QString::fromLocal8Bit() 显示乱码。

在 main 函数头文件下面添加以下语句

#if defined(_MSC_VER) && (_MSC_VER >= 1600)
#pragma execution_character_set("utf-8")
#endif

或者在 .pro 文件中添加

QMAKE_CXXFLAGS += /utf-8
  • Local 字符集设置为其它编码方式,如 GBK

和上述“没有设置 Local 字符集”相同,直接输入、采用 QString::fromUtf8() 都显示乱码,QString::fromLocal8Bit() 显示正常

如果进行以下设置,则直接输入、采用 QString::fromUtf8() 都显示正常,而 QString::fromLocal8Bit() 显示乱码。

在 main 函数头文件下面添加以下语句

#if defined(_MSC_VER) && (_MSC_VER >= 1600)
#pragma execution_character_set("utf-8")
#endif

或者在 .pro 文件中添加

QMAKE_CXXFLAGS += /utf-8

情况2:如果是MinGW编译器

(1) 源文件编码设置为 GBK

  • Local 字符集设置为 GBK

采用 QString::fromLocal8Bit() 对中文进行转码,则显示正常,否则乱码;因为设置了本地字符集为 GBK 编码,fromLocal8Bit() 就是将中文转换为 GBK 编码。

  • 没有设置 Local 字符集

如果没有设置 Local 字符集编码方式,则 Qt 采用默认的系统字符编码方式 GBK,和上述 “Local 字符集设置为 GBK” 转码方法相同。

  • Local 字符集设置为其它编码方式,如 UTF-8

中文显示乱码,因为无法将显示文字的编码方式设为 GBK。

(2) 源文件编码设置为 UTF-8(无 BOM)

  • Local 字符集设置为 UTF-8

直接输入中文(QString 默认将 char * 当作是 UTF-8 编码去构造)、采用 QString::fromLocal8Bit()(此时本地字符集为 UTF-8)、采用 QString::fromUtf8() 都能显示正常。

  • 没有设置 Local 字符集

如果没有设置 Local 字符集编码方式,则 Qt 采用默认的系统字符编码方式 GBK,QString::fromLocal8Bit()(此时本地字符集为 GBK)则显示乱码,直接输入、采用 QString::fromUtf8() 都能显示正常。

  • Local 字符集设置为其它编码方式,如 GBK

和上述“没有设置 Local 字符集”相同,QString::fromLocal8Bit()(此时本地字符集为 GBK)则显示乱码,直接输入、采用 QString::fromUtf8() 都能显示正常。

(3) 源文件编码设置为 UTF-8(有 BOM)

UTF-8 有 BOM和无 BOM 对 MinGW 编译器没有影响。

  • Local 字符集设置为 UTF-8

直接输入中文(QString 默认将 char * 当作是 UTF-8 编码去构造)、采用 QString::fromLocal8Bit()(此时本地字符集为 UTF-8)、采用 QString::fromUtf8() 都能显示正常。

  • 没有设置 Local 字符集

如果没有设置 Local 字符集编码方式,则 Qt 采用默认的系统字符编码方式 GBK,QString::fromLocal8Bit()(此时本地字符集为 GBK)则显示乱码,直接输入(等价于 QString::fromUtf8())、采用 QString::fromUtf8() 都能显示正常。

  • Local 字符集设置为其它编码方式,如 GBK

和上述“没有设置 Local 字符集”相同,QString::fromLocal8Bit()(此时本地字符集为 GBK)则显示乱码,直接输入、采用 QString::fromUtf8() 都能显示正常。

在 Qt5 中使用 QString::QStringLiteral 这个宏来处理中文乱码问题,这个宏是官方提供专门处理乱码问题的。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yann@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值