jjzjj

c++ - 为什么汉字经过编译器会变成乱码?

coder 2024-06-08 原文

所以我正在编写一个程序,将一个中英定义的 .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

Picture of Input file & Output

最佳答案

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

您的问题与“charwchar_t”无关。其实你的程序没有任何问题!只有当文本通过 cout 离开并到达终端时才会出现问题。


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

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

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

尝试运行它,您应该会在两个平台(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(“终端”、“控制台”、“shell”,无论您如何调用它)。 CLI 从 cout 读取字节并将它们解码为字符以便显示。

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

  • 另一方面,Windows 终端通常配置为使用系统相关的解码器(您的似乎是 DOS codepage 437 )。坏消息! 您的字节是 UTF-8 编码的,因此您的 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 后,它看起来基本没问题:

有些字符仍然乱七八糟,但那是因为字体不支持它们。

关于c++ - 为什么汉字经过编译器会变成乱码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53294124/

有关c++ - 为什么汉字经过编译器会变成乱码?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  5. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  6. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  7. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  8. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  9. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  10. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

随机推荐