系列文章

一、输出测试

1. 测试代码

使用了一个非常简单的代码进行测试:

1
2
3
4
5
6
7
8
#include <iostream>

int main()
{
std::cout << "你好,C++!" << std::endl;

return 0;
}

2. 测试前分析

开始测试前我们不妨先分析一下,根据上一篇文章:【编程中乱码出现的原因】 所提到的关于导致乱码的两个决定性因素不难得出,我们只需要将编译器的设定1(输入源文件所使用字符集的设定项)固定为utf-8(因为在上一篇文章中将源文件假设固定为了utf-8字符集),将编译器的设定2(输出窄字符串的转换目标字符集的设定项)固定为gbk(在上一篇文章中假设固定为了gbk字符集)即可。

由于 MSVC 的设定1默认不识别utf-8,而 GCC 的设定2默认为utf-8,所以很显然,如果我们使用 MSVC 进行编译,则需要显式设置设定1,而如果使用 GCC 进行编译,则需要显示设置设定2,所以实际上只需要做两组测试即可。

但为了证明上一篇文章中所述理论的可靠性,还需要添加一些测试组,所有测试将分为以下几个部分:

  1. 可靠性测试:又分为以下两个部分
    1. 预测正确测试组:编译器在缺省状态下对utf-16(LE)utf-8(BOM)以及gbk字符集的源文件进行编译测试(一共 3 组测试)
    2. 预测出错测试组:编译器在缺省状态下对utf-8(No BOM)字符集的源文件进行编译测试(一共 1 组测试)
  2. 正确性测试:也分为两个部分
    1. 预测正确测试组:将设定1设置为utf-8utf-8(No BOM)字符集的源文件进行编译测试(一共 1 组测试)
    2. 预测错误对照组:将设定2设置为utf-8utf-16(LE)utf-8(BOM)utf-8(No BOM)以及gbk字符集的源文件进行编译测试(一共 4 组测试)
  1. 可靠性测试:此部分无预测正确测试组,只包含预测错误测试组。分别是在缺省状态下对utf-16(LE)utf-8(BOM)utf-8(No BOM)以及gbk字符集的源文件进行编译测试(一共 4 组测试)
  2. 正确性测试:此部分分为如下两个部分
    1. 预测正确测试组:将设定2固定为gbk,然后将设定1依次设置为utf-16gbk并对对应字符集的源文件进行编译测试,另外在缺省状态下对utf-8(No BOM)utf-8(BOM)字符集的源文件进行编译测试(一共 4 组测试)
    2. 预测错误对照组:将设定2固定为gbk设定1保持缺省状态对utf-16gbk字符集的源文件进行编译测试。(一共 2 组测试)

3. 测试及测试结果

以上测试均以表格形式给出:

  1. 可靠性测试

    1. 预测正确测试组

      设定项/组别 第一组(utf-16(LE) 第二组(utf-8(BOM) 第三组(gbk
      全缺省状态 正确输出 正确输出 正确输出
    2. 预测出错测试组

      设定项/组别 第一组(utf-8(No BOM)
      全缺省状态 出现乱码浣犲ソ锛孋++!
  2. 正确性测试

    1. 预测正确测试组

      设定项/组别 第一组(utf-8(No BOM)
      设定1utf-8 正确输出
    2. 预测错误对照组

      设定项/组别 第一组(utf-16(LE) 第二组(utf-8(BOM) 第三组(utf-8(No BOM) 第四组(gbk
      设定2utf-8 出现乱码浣犲ソ锛孋++! 出现乱码浣犲ソ锛孋++! 出现乱码娴g姴銈介敍瀛?+! 出现乱码浣犲ソ锛孋++!

分析:所有测试组都得到了预想的结果,比较不同的是正确性测试中的预测错误对照组中的第三组中出现的乱码和其他组别不相同,其原因应该是其他三组都正确识别了源文件中的内容,但在输出时与终端字符集不匹配,而第三组则是在识别源文件内容时就出现了错误,然后在输出时又因为一次不匹配导致了该乱码。

  1. 可靠性测试

    设定项/组别 第一组(utf-16(LE) 第二组(utf-8(BOM) 第三组(utf-8(No BOM) 第四组(gbk
    全缺省状态 出现编译错误 出现乱码浣犲ソ锛孋++! 出现乱码浣犲ソ锛孋++! 正确输出
  2. 正确性测试

    1. 预测正确测试组

      设定项/组别 第一组(utf-16(LE) 第二组(utf-8(BOM) 第三组(utf-8(No BOM) 第四组(gbk
      设定1utf-16
      设定2gbk
      出现编译错误(和可靠性测试中的编译错误不同)
      设定1gbk
      设定2gbk
      正确输出
      设定2gbk 正确输出
      设定2gbk 正确输出
    2. 预测错误对照组

      设定项/组别 第一组(utf-16(LE) 第二组(gbk
      设定2gbk 出现编译错误(和可靠性测试中的编译错误相同) 出现编译错误(输出字符串被识别为非法序列)

分析:相比于 MSVC 的测试结果,GCC 的测试结果有了更多可以说道的地方

  1. 首先是在可靠性测试的预测错误测试中,最后一组在缺省状态下对gbk字符集的源文件进行编译测试得到了正确的输出,其原因应该是在读取阶段用utf-8解释了gbk字符串编码,但由于 GCC 本身的设定1设定2在缺省状态下是相同的,所以即便字符串编码被错误解释了,但是却并未进行任何转换,所以最后在输出到gbk字符集的终端时得到了正确的结果
  2. 整个测试中在对utf-16(LE)字符集的源文件进行测试时都得到了编译错误,其中第一次和第三次错误相同,第二次出现了不同的错误。根据这几组测试中的设定项可以知道第一次和第三次都是用utf-8去解释了utf-16(LE)字符集的源文件,而第二次虽然使用utf-16去解释了utf-16(LE)字符集的源文件,但仍然得到了编译错误,这可能是因为设定项并未设置正确,因为utf-16一共有 4 种情况,分别是大端和小端以及是否带 BOM。但在 GCC 的选项中我只找到了utf-16这一个值,同时我用来测试的是 vscode,其中对于utf-16只进行了大端小端的区分。
  3. 最后一个错误是最后一个测试组中的第二组,即用utf-8识别gbk字符集的源文件,出现了编译错误。但虽然说是编译错误,但具体问题仍然出现在字符串的识别上。在 MSVC 的测试组中并没有用utf-8识别gbk文件的测试组,所以我再次用 MSVC 进行了该项测试,结果与 GCC 出现相同错误,故此处的编译错误属于正常现象。

二、输入反馈测试

1. 测试代码

1
2
3
4
5
6
7
8
9
#include <iostream>

int main()
{
char str[20];
std::cout << "你的输入是:" << str << std::endl;

return 0;
}

2. 测试结果及分析

对于输入反馈的测试,此处不再详细描述测试环节。

测试结果

实际上,除了上述测试中出现编译错误的测试组外,其余测试无论输出是否正确,几乎都有正确的输入反馈。

唯一出现的一个乱码错误是在使用gbk解释utf-8字符集的源文件并将字符串字面值按照utf-8字符集下的编码输出到gbk环境的终端上的测试组。另外,此错误只出现在了 MSVC 的测试中,在 GCC 中无法复现此错误,而是直接返回一个编译错误:error: failure to convert gbk to UTF-8

分析

由于几乎每一组都没有出现错误,所以我推测输入的字符串与编译器的设定项没有关系,并且如果在程序中没有对输入的字符串进行任何处理而直接输出的话,其编码也不会进行任何变化(理所当然)。

在输入阶段,字符串就已经通过终端转换为了gbk字符集下的编码,然后在程序中转了一圈此编码又输出到了终端,所以不会出现错误。

而仅出现的那一个错误后续又设置了两组测试,一是将字符串字面值全删了,仅进行输入反馈,二是在原本的字符串字面值末尾添加了几个英文字符。在第一组测试中输入得到了正确的反馈,而在第二组测试中,英文字符部分的前几个字符未能成功显示,而后续字符以及输入反馈都得到了正确的显示。推测是在整个字符串从识别到输出的过程中中文字符串的末尾编码与后续字符串的前部分编码产生了合并,总之也属于乱码的一种,但输入反馈仍然满足上述的推测。

三、总结

根据上述测试,我们已经可以下定论了,只要我们将编译器的设定1与源文件字符集匹配,将设定2与终端字符集匹配,则可以做到最低程度上的不乱码。

而至于要处理输入字符串或者字符串字面值的情况,则暂时不进行讨论,可能在后续的文章中进行探索。