首页 > 解决方案 > 为什么汉字经过编译器后会变成乱码?

问题描述

所以我正在编写一个程序,将一个中英文定义的.txt文件变成一个通过CLI运行的词汇训练器。但是,在 Windows 中,当我尝试在 VS2017 中编译它时,它变成了乱码,我不知道为什么。我认为它在 linux 中运行良好,但 windows 似乎把它搞砸了。这和windows中的编码表有关系吗?我错过了什么吗?我在 Linux 中编写了代码以及输入文件,但我尝试使用 windows IME 编写字符,结果仍然相同。我认为这张照片最能说明问题。谢谢

注意:根据要求添加了在 Windows 中显示的输入/输出示例。此外,输入是 UTF-8。

输入样本

人(rén),person
刀(dāo),knife
力(lì),power
又(yòu),right hand; again
口(kǒu),mouth

输出样本

人(rén),person
刀(dāo),knife
力(lì),power
又(yòu),right hand; again
口(kǒu),mouth
土(tǔ),earth

输入文件和输出的图片

标签: c++linuxwindowsvisual-studiounicode

解决方案


TL;DR:Windows 终端讨厌 Unicode。你可以解决它,但它并不漂亮。

char您在这里的问题与“对”无关wchar_t。其实你的程序没有问题!只有当文本离开cout并到达终端时才会出现问题。


您可能习惯于将 achar视为“字符”;这是一个常见的(但可以理解的)误解。在 C/C++ 中,char类型通常与8 位整数同义,因此更准确地描述为字节

您的文本文件chineseVocab.txt被编码为 UTF-8。当你通过 读取这个文件时fstream,你得到的是一串 UTF-8 编码的字节

I/O 中没有“字符”之类的东西;您总是以特定的编码传输字节。在您的示例中,您正在从文件句柄 ( )中读取 UTF-8 编码的字节。fin

尝试运行它,您应该在两个平台(Windows 和 Linux)上看到相同的结果:

int main()
{
    fstream fin("chineseVocab.txt");
    string line;
    while (getline(fin, line))
    {
        cout << "Number of bytes in the line: " << dec << line.length() << endl;
        cout << "    ";
        for (char c : line)
        {
            // Here we need to trick the compiler into displaying this "char" as an integer:
            unsigned int byte = (unsigned char)c;
            cout << hex << byte << "  ";
        }
        cout << endl;
        cout << endl;
    }
    return 0;
}

这是我在我的(Windows)中看到的:

Number of bytes in the line: 16
    e4  ba  ba  28  72  c3  a9  6e  29  2c  70  65  72  73  6f  6e

Number of bytes in the line: 15
    e5  88  80  28  64  c4  81  6f  29  2c  6b  6e  69  66  65

Number of bytes in the line: 14
    e5  8a  9b  28  6c  c3  ac  29  2c  70  6f  77  65  72

Number of bytes in the line: 27
    e5  8f  88  28  79  c3  b2  75  29  2c  72  69  67  68  74  20  68  61  6e  64  3b  20  61  67  61  69  6e

Number of bytes in the line: 15
    e5  8f  a3  28  6b  c7  92  75  29  2c  6d  6f  75  74  68

到现在为止还挺好。


问题从现在开始:您想将那些相同的 UTF-8 编码字节写入另一个文件句柄 ( cout)。

cout文件句柄连接到您的 CLI(“终端”、“控制台”、“外壳”,无论您想怎么称呼它)。CLI 从中读取字节cout并将其解码为字符以便显示。

  • Linux 终端通常配置为使用UTF-8 解码器。好消息!您的字节是 UTF-8-encoded,因此您的 Linux 终端的解码器文本文件的编码相匹配。这就是终端中一切看起来都不错的原因。

  • 另一方面,Windows 终端通常配置为使用依赖于系统的解码器(您的似乎是DOS 代码页 437)。坏消息!您的字节是 UTF-8-encoded,因此您的 Windows 终端的解码器与文本文件的编码不匹配。这就是为什么终端中的一切看起来都是乱码的原因。


好的,那你怎么解决这个问题?不幸的是,我找不到任何可移植的方式来做到这一点......您需要将您的程序分成 Linux 版本和 Windows 版本。在 Windows 版本中:

  1. 将您的 UTF-8 字节转换为 UTF-16 代码单元。
  2. 将标准输出设置为 UTF-16 模式。
  3. 写到wcout而不是cout
  4. 告诉您的用户将他们的终端更改为支持中文字符的字体。

这是代码:

#include <fstream>
#include <iostream>
#include <string>

#include <windows.h>

#include <fcntl.h>  
#include <io.h>  
#include <stdio.h> 

using namespace std;

// Based on this article:
// https://msdn.microsoft.com/magazine/mt763237?f=255&MSPPError=-2147217396
wstring utf16FromUtf8(const string & utf8)
{
    std::wstring utf16;

    // Empty input --> empty output
    if (utf8.length() == 0)
        return utf16;

    // Reject the string if its bytes do not constitute valid UTF-8
    constexpr DWORD kFlags = MB_ERR_INVALID_CHARS;

    // Compute how many 16-bit code units are needed to store this string:
    const int nCodeUnits = ::MultiByteToWideChar(
        CP_UTF8,       // Source string is in UTF-8
        kFlags,        // Conversion flags
        utf8.data(),   // Source UTF-8 string pointer
        utf8.length(), // Length of the source UTF-8 string, in bytes
        nullptr,       // Unused - no conversion done in this step
        0              // Request size of destination buffer, in wchar_ts
    );

    // Invalid UTF-8 detected? Return empty string:
    if (!nCodeUnits)
        return utf16;

    // Allocate space for the UTF-16 code units:
    utf16.resize(nCodeUnits);

    // Convert from UTF-8 to UTF-16
    int result = ::MultiByteToWideChar(
        CP_UTF8,       // Source string is in UTF-8
        kFlags,        // Conversion flags
        utf8.data(),   // Source UTF-8 string pointer
        utf8.length(), // Length of source UTF-8 string, in bytes
        &utf16[0],     // Pointer to destination buffer
        nCodeUnits     // Size of destination buffer, in code units          
    );

    return utf16;
}

int main()
{
    // Based on this article:
    // https://blogs.msmvps.com/gdicanio/2017/08/22/printing-utf-8-text-to-the-windows-console/
    _setmode(_fileno(stdout), _O_U16TEXT);

    fstream fin("chineseVocab.txt");
    string line;
    while (getline(fin, line))
        wcout << utf16FromUtf8(line) << endl;
    return 0;
}

在我的终端中,将字体更改为MS Gothic后,它看起来基本正常:

大多数汉字看起来还可以

有些字符仍然混乱,但这是由于字体不支持它们。


推荐阅读