功能描述:最全的 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) |
1200 | Unicode |
1201 | Unicode (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) |
65000 | Unicode (UTF-7) |
65001 | Unicode (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 | |||
源代码的编码 | 编译器 | 显示正常 | 显示乱码 |
GBK | win 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 这个宏来处理中文乱码问题,这个宏是官方提供专门处理乱码问题的。