5

摘要:我应该怎么做才能将源代码中定义的以 UTF-8 编码 (Windows CP 65001) 存储的字符串文字正确打印到使用流的cmd控制台?std::cout

动机:我想修改优秀的Catch 单元测试框架(作为一个实验),以便它显示我的带有重音字符的文本。修改应该简单、可靠,并且对其他语言和工作环境也应该有用,以便作者可以接受它作为增强。或者,如果您知道 Catch 并且如果有其他解决方案,您可以发布吗?

详情:我们先从捷克版的“快棕狐……”开始吧

#include <iostream>
#include "windows.h"

using namespace std;

int main()
{
    cout << "\n-------------------------- default cmd encoding = 852 -------------------\n";
    cout << "Příšerně žluťoučký kůň úpěl ďábelské ódy!" << endl;

    cout << "\n-------- Windows Central European (1250) set for the cmd console --------\n";
    SetConsoleOutputCP(1250);
    std::cout << "Příšerně žluťoučký kůň úpěl ďábelské ódy!" << std::endl;

    cout << "\n------------- Windows UTF-8 (65001) set for the cmd console -------------\n";
    SetConsoleOutputCP(CP_UTF8);
    std::cout << "Příšerně žluťoučký kůň úpěl ďábelské ódy!" << std::endl;
}

它打印以下内容(字体设置为 Lucida Console):在此处输入图像描述

默认编码为 852 ,cmd默认 windows 编码为 1250,源代码使用 65001 编码(带有 BOM 的 UTF-8)保存。SetConsoleOutputCP(1250);更改编码(以编程方式)的方式与更改cmd编码相同chcp 1250

观察:设置1250编码时,UTF-8字符串文字打印正确。相信是可以解释的,但是真的很奇怪。有什么体面的、人性化的、通用的方法来解决这个问题吗?

更新:在我的"narrow string literal"情况下使用 Windows-1250 编码存储(中欧的本地 Windows 编码)。它似乎与源代码的编码无关。编译器将其保存在windows 本机编码中。因此,切换cmd到该编码可以提供所需的输出。这很丑陋,但是我怎样才能以编程方式获取本机 Windows 编码(将其传递给SetConsoleOutputCP(cpX))?我需要的是一个对发生编译的机器有效的常量。它不应该是运行可执行文件的机器的本机编码。

也引入了 C++11 u8"the UTF-8 string literal",但似乎不适合SetConsoleOutputCP(CP_UTF8);

4

2 回答 2

2

这是通过 luk32 跳转链接并确认 Melebius 评论找到的部分答案(见问题下方)。这不是完整的答案,我很乐意接受您的后续评论。

我刚刚找到了触及问题的UTF-8 Everywhere Manifesto。要点17. 问:如何在我的 C++ 代码中编写 UTF-8 字符串文字?说(对于 Microsoft C++ 编译器也是明确的):

然而,最直接的方法是按原样编写字符串并保存以 UTF-8 编码的源文件:

                                "∃y ∀x ¬(x ≺ y)"

不幸的是,MSVC 将其转换为一些 ANSI 代码页,从而破坏了字符串。要解决此问题,请将文件保存为不带BOM 的 UTF-8。MSVC 将假定它位于正确的代码页中并且不会触及您的字符串。但是,它使得无法使用 Unicode 标识符和宽字符串文字(无论如何您都不会使用)。

我真的很喜欢宣言。简而言之,使用粗鲁的话,并且可能过于简单化,它说:

忽略wstring,wchar_t和类似的东西。忽略代码页。忽略字符串文字前缀,如L, u, U, u8。到处使用 UTF-8。写下所有文字"naturally"。确保它也存储在已编译的二进制文件中。

如果下面的代码是用UTF-8 不带 BOM存储的...

#include <iomanip>
#include <iostream>
#include "windows.h"

using namespace std;

int main()
{
    SetConsoleOutputCP(CP_UTF8);
    cout << "Příšerně žluťoučký kůň úpěl ďábelské ódy!" << endl;

    int cnt = 0;
    for (unsigned int c : "Příšerně žluťoučký kůň úpěl ďábelské ódy!") 
    {
        cout << hex << setw(2) << setfill('0') << (c & 0xff);
        ++cnt;
        if (cnt % 16 == 0)      cout << endl;
        else if (cnt % 8 == 0)  cout << " | ";
        else if (cnt % 4 == 0)  cout << "  ";
        else                    cout << ' ';
    }
    cout << endl;
}

它打印(应该是 UTF-8 编码)......

在此处输入图像描述

将源代码保存为带有 BOM 的 UTF-8 时,它会打印出不同的结果...

在此处输入图像描述

但是,问题仍然存在——如何以编程方式设置控制台编码,以便正确打印 UTF-8 字符串。

我放弃。cmd控制台只是残废了,不值得从外面修理它。我接受我自己的评论只是为了结束这个问题。如果有人找到与 Catch 单元测试框架相关的体面解决方案(可能完全不同),我将很高兴接受他/她的评论作为答案。

于 2015-09-01T14:45:41.097 回答
0

MSVC 编译器尝试使用本地编码对代码中的 const 字符串进行编码。在您的情况下,它使用code page 852. 因此,即使您的 cmd 输出尝试使用 读取和输出字符串code page 1250,该字符串实际上也是使用 存储的code page 852。存储和读取之间的这种不兼容会产生错误的输出。
解决此问题的一种方法是将字符串存储在使用code page 1250. Visual Studio Code提供这样的功能。您可以将文件作为二进制文件(即逐字节)读取到 char 缓冲区,然后输出缓冲区。

char * memblock = new char[1024];
std::ifstream file("src.txt", std::ios::in | std::ios::binary | std::ios::ate);
int size;
if (file.is_open())
{
    size = file.tellg();
    memblock = new char[size];
    file.seekg(0, std::ios::beg);
    file.read(memblock, size);
    file.close();
}
else
{
    std::cout << "File not opened." << std::endl;
}
memblock[size] = 0;
std::cout << memblock << std::endl;

在此处输入图像描述

于 2018-02-18T04:21:54.213 回答