如何在Windows上将UTF-8字符串打印到std::cout?

6qfn3psc  于 2023-10-22  发布在  Windows
关注(0)|答案(9)|浏览(161)

我正在用C++编写一个跨平台应用程序。所有字符串都是内部UTF-8编码的。考虑以下简化代码:

#include <string>
#include <iostream>

int main() {
    std::string test = u8"Greek: αβγδ; German: Übergrößenträger";
    std::cout << test;

    return 0;
}

在Unix系统上,std::cout期望8位字符串是UTF-8编码的,因此这段代码可以正常工作。
然而,在Windows上,std::cout期望8位字符串为Latin-1或类似的非Unicode格式(取决于代码页)。这将导致以下输出:
希腊语:;德语:Berger Berger

在Windows上,如何使std::cout将8位字符串解释为UTF-8?

这就是我所尝试的:

#include <string>
#include <iostream>
#include <io.h>
#include <fcntl.h>

int main() {
    _setmode(_fileno(stdout), _O_U8TEXT);
    std::string test = u8"Greek: αβγδ; German: Übergrößenträger";
    std::cout << test;

    return 0;
}

我希望_setmode能起作用。但是,这会在调用operator<<的行中导致以下Assert错误:
Microsoft Visual C运行时库
调试Assert失败!
程序:d:\visual studio 2015\Projects\utf8test\logg\utf8test.exe文件:minkernel\crts\ucrt\src\appcrt\stdio\fputc.cpp行:47
表达式:((_Stream.is_string_backed())||(fn = _fileno(_Stream.public_stream()),((_textmode_safe(fn)== __crt_lowio_text_mode::ansi)&&!_tm_unicode_safe(fn)
有关程序如何导致Assert失败的信息,请参见有关Assert的Visual C
文档。

sh7euo9m

sh7euo9m1#

最后,我让它工作了。这个答案结合了Miles Budnek,Paul和mkluwe的意见以及我自己的一些研究。首先,让我从 * 将在Windows 10* 上工作的代码开始。在那之后,我会带你看一下代码,并解释为什么它不能在Windows 7上开箱即用。

#include <string>
#include <iostream>
#include <Windows.h>
#include <cstdio>

int main() {
    // Set console code page to UTF-8 so console known how to interpret string data
    SetConsoleOutputCP(CP_UTF8);

    // Enable buffering to prevent VS from chopping up UTF-8 byte sequences
    setvbuf(stdout, nullptr, _IOFBF, 1000);

    std::string test = u8"Greek: αβγδ; German: Übergrößenträger";
    std::cout << test << std::endl;
}

代码首先设置代码页as suggested by Miles Budnik。这将告诉控制台将接收到的字节流解释为UTF-8,* 而不是 * ANSI的某种变体。
接下来,Visual Studio附带的STL代码中有一个问题。std::cout将其数据打印到std::basic_filebuf类型的流缓冲区。当该缓冲区接收到一个字符串(通过std::basic_streambuf::sputn())时,它不会将其作为一个整体传递给底层文件。相反,它将单独传递每个字节。As explained by mkluwe,如果控制台接收到UTF-8字节序列 * 作为单个字节 *,它不会将它们解释为单个代码点。相反,它会将它们视为多个字符。UTF-8字节序列中的每个字节本身都是一个无效的代码点,所以你会看到的是""。有a related bug report for Visual Studio,但它被关闭为By Design。解决方法是为流启用缓冲。作为一个额外的奖励,这将给你给予更好的表现。但是,您现在可能需要定期刷新流,就像我对std::endl所做的那样,否则您的输出可能不会显示。
最后,Windows控制台支持光栅字体和TrueType字体。正如Paul所指出的,光栅字体将简单地忽略控制台的代码页。因此,只有当控制台设置为TrueType字体时,非ASCII Unicode字符才有效。在Windows 7之前,默认字体是光栅字体,因此用户必须手动更改它。幸运的是,Windows 10 changes the default font to Consolas,所以这部分问题应该会随着时间的推移而自行解决。

1aaf6o9v

1aaf6o9v2#

问题不在于std::cout,而在于windows控制台。使用C-stdio,在设置UTF-8代码页(使用SetConsoleOutputCPchcp)* 和 * 在cmd的设置中设置Unicode支持字体后,您将获得üfputs( "\xc3\xbc", stdout );(Consolas应该是support over 2000 characters,并且有注册表黑客可以向cmd添加更多功能字体)。
如果你用putc('\xc3'); putc('\xbc');一个字节接一个字节地输出,你会得到双豆腐,因为控制台会把它们单独解释为非法字符。这可能就是C++流所做的。
参见UTF-8 output on Windows console进行详细讨论。
对于我自己的项目,我最终实现了一个std::stringbuf来转换到Windows-1252。如果你真的需要完整的Unicode输出,这将不会真正帮助你,但是。
另一种方法是使用fputs的streambuf作为实际输出:

#include <iostream>
#include <sstream>

#include <Windows.h>

class MBuf: public std::stringbuf {
public:
    int sync() {
        fputs( str().c_str(), stdout );
        str( "" );
        return 0;
    }
};

int main() {
    SetConsoleOutputCP( CP_UTF8 );
    setvbuf( stdout, nullptr, _IONBF, 0 );
    MBuf buf;
    std::cout.rdbuf( &buf );
    std::cout << u8"Greek: αβγδ\n" << std::flush;
}

我在这里关闭了输出缓冲,以防止它干扰未完成的UTF-8字节序列。

okxuctiv

okxuctiv3#

std::cout正在做它应该做的事情:它会将UTF-8编码的文本沿着发送到控制台,但控制台将使用其当前代码页解释这些字节。您需要将程序的控制台设置为UTF-8代码页:

#include <string>
#include <iostream>
#include <Windows.h>

int main() {
    std::string test = u8"Greek: αβγδ; German: Übergrößenträger";
    SetConsoleOutputCP(CP_UTF8);
    std::cout << test;
}

如果Windows将默认代码页切换为UTF-8,那就太好了,但由于向后兼容性问题,他们可能无法做到。

gdx19jrr

gdx19jrr4#

忘记你所知道的关于Windows控制台及其Unicode/UTF-8支持(或者说缺乏支持)的一切。这是2020年,这是一个新的世界。这不是对上述问题的直接回答,而是一种现在更有意义的替代方案,一种以前不可能的新方法。
每个人都是对的,根本问题是Windows控制台。但有一个新的球员在城里,它的Windows终端。安装并启动Windows终端。使用此程序:

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

int main()
{
    SetConsoleOutputCP(CP_UTF8); 
    // or have your user set the console codepage: `chcp 65001`
    
    std::cout << "\"u\" with two dots on top: \xc3\xbc\n";
    std::cout << "chinese glyph for \"world\": \xe5\x80\xbc\n";
    std::cout << "smiling emoji: \xf0\x9f\x98\x80\n";
    return 0;
}

这个程序通过普通的cout发送UTF-8。
输出:

命令chcp 65001SetConsoleOutputCP(CP_UTF8)是Windows终端中的cmd选项卡所必需的,但它看起来并不在Powershell选项卡中。Powershell是否默认为UTF-8?
在我看来,根除核心问题cmd是现在最好的选择。传出去

k2arahey

k2arahey5#

使用以下Windows API调用将控制台输出编码设置为UTF-8:

SetConsoleOutputCP(65001);

该函数的文档可以在Windows Dev Center上找到。

ruarlubt

ruarlubt6#

自从我开始使用{fmt}库,我所有的编码问题都消失了。
一个简单的用途:

#include <fmt/core.h>

int main() {
  fmt::print("Greek: αβγδ; German: Übergrößenträger\n");
}
trnvg8h3

trnvg8h37#

即使更改了代码页,某些Unicode字符也无法在控制台窗口中正确显示,因为您的字体不支持它。例如,如果要显示阿拉伯字符,则需要安装支持阿拉伯语的字体。
This stackoverflow page应该会有帮助。
顺便说一句,Unicode版本的控制台API(如WriteConsoleW)不会来拯救,因为它们在内部调用相应的Windows代码页版本API(如WriteConsoleA)。std::wcout也没有帮助,因为它会在内部将wchar_t字符串转换为char字符串。
看来windows控制台窗口不支持Unicode,我建议你使用MessageBox代替。

2j4z5cfb

2j4z5cfb8#

我也遇到了同样的问题,为此编写了一个非常小的库,名为libpu 8https://github.com/jofeu/libpu8
对于windows控制台,它取代了cin,cout和cerr的streambufs,以便它们在前端接受并生成utf-8,并以UTF-16与控制台对话。在非windows操作系统上,或者如果cin,cout,cerr被附加到文件/管道而不是控制台,它什么也不做。它还将C++ main()函数的参数转换为Windows上的UTF-8。
使用示例:

#include <libpu8.h>
#include <string>
#include <fstream>
#include <windows.h>

// argv are utf-8 strings when you use main_utf8 instead of main.
// main_utf8 is a macro. On Windows, it expands to a wmain that calls
// main_utf8 with converted strings.
int main_utf8(int argc, char** argv)
{
        // this will also work on a non-Windows OS that supports utf-8 natively
        std::ofstream f(u8widen(argv[1]));
        if (!f)
        {
                // On Windows, use the "W" functions of the windows-api together
                // with u8widen and u8narrow
                MessageBoxW(0,
                        u8widen(std::string("Failed to open file ") + argv[1]).c_str(), 0, 0);
                return 1;
        }
        std::string line;
        // line will be utf-8 encoded regardless of whether cin is attached to a
        // console, or a utf-8 file or pipe.
        std::getline(std::cin, line);
        // line will be displayed correctly on a console, and will be utf-8 if
        // cout is attached to a file or pipe.
        std::cout << "You said: " << line;
        return 0;
}
p8h8hvxi

p8h8hvxi9#

自STD C++ 20起

#include <iostream>
#include <string>
#ifdef _WIN32
#include <windows.h>
#include <clocale>
#endif

int main() {
#ifdef _WIN32
  // console UTF-8
  std::setlocale(LC_CTYPE, ".UTF8");
  SetConsoleOutputCP(CP_UTF8);
  SetConsoleCP(CP_UTF8);
#endif

  std::string str = "𨉟呐㗂越 🤑 αβγδ ñ";
  std::cout << str << std::endl;

  return 0;
}

使用clang:clang版本17.0.2目标:x86_64-pc-windows-msvc线程模型:POSIX
对于MSVC,你必须做同样的事情,但添加/UTF-8标志:

set(CMAKE_CXX_STANDARD 20)
target_compile_options(hello_world PRIVATE /source-charset:utf-8 /execution-charset:utf-8)

https://github.com/microsoft/STL/issues/4110

相关问题