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

Dan*_*olf 18 c++ windows encoding utf-8

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

#include <string>
#include <iostream>

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

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在Unix系统上,std::cout期望8位字符串是UTF-8编码的,所以这段代码工作正常.

但是,在Windows上,要求std::cout8位字符串采用Latin-1或类似的非Unicode格式(取决于代码页).这导致以下输出:

希腊语:????????; 德语:?£bergr?Âentr?ñger

如何std::cout在Windows上将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;
}
Run Code Online (Sandbox Code Playgroud)

我希望_setmode能做到这一点.但是,这会在调用的行中导致以下断言错误operator<<:

Microsoft Visual C++运行时库

调试断言失败!

程序:d:\ visual studio 2015\Projects\utf8test\Debug\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))))

有关程序如何导致断言失败的信息,请参阅有关断言的Visual C++文档.

Dan*_*olf 12

最后,我得到了它的工作.这个答案将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;
}
Run Code Online (Sandbox Code Playgroud)

代码开始于设置代码页,如Miles Budnik所建议的那样.这将告诉控制台将它接收的字节流解释为UTF-8,而不是 ANSI的某些变体.

接下来,Visual Studio附带的STL代码存在问题.std::cout将其数据打印到类型的流缓冲区std::basic_filebuf.当该缓冲区收到一个字符串(via std::basic_streambuf::sputn())时,它不会将它作为一个整体传递给底层文件.相反,它将分别传递每个字节.正如mkluwe所解释的,如果控制台收到UTF-8字节序列作为单个字节,它将不会将它们解释为单个代码点.相反,它会将它们视为多个字符.UTF-8字节序列中的每个字节本身都是一个无效的代码点,所以你会看到 's.Visual Studio一个相关的错误报告,但它已按"设计"关闭.解决方法是为流启用缓冲.作为额外的奖励,这将为您提供更好的表现.但是,您现在可能需要像我一样定期刷新流std::endl,否则您的输出可能不会显示.

最后,Windows控制台支持光栅字体和TrueType字体.正如Paul所指出的,光栅字体将忽略控制台的代码页.因此,只有将控制台设置为TrueType字体时,非ASCII Unicode字符才有效.在Windows 7之前,默认为光栅字体,因此用户必须手动更改它.幸运的是,Windows 10将默认字体更改为Consolas,因此问题的这一部分应该随着时间的推移而自行解决.


mkl*_*uwe 7

问题不std::cout在于Windows控制台.使用C-stdio,您将在设置UTF-8代码页(使用或)在cmd的设置中设置Unicode支持字体后获得üwith (Consolas应支持超过2000个字符,并且有注册表黑客可以向cmd添加更多功能的字体) .fputs( "\xc3\xbc", stdout );SetConsoleOutputCPchcp

如果你输出一个字节,putc('\xc3'); putc('\xbc');你将得到双豆腐,因为控制台将它们分别解释为非法字符.这可能是C++流所做的.

请参阅Windows控制台上的UTF-8输出以进行长时间的讨论.

对于我自己的项目,我最终实现了std::stringbuf转换到Windows-1252.我真的需要完整的Unicode输出,但这对你没有帮助.

另一种方法是覆盖cout'streambuf,fputs用于实际输出:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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


Mil*_*nek 6

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;
}
Run Code Online (Sandbox Code Playgroud)

如果Windows将默认代码页切换为UTF-8会很好,但由于向后兼容性问题,它们可能无法实现.

  • 这似乎对我不起作用。我使用 Visual Studio 构建了您的代码并从“cmd”运行它。输出与没有调用“SetConsoleOutputCP”时完全相同。 (2认同)
  • 另一个想法:"SetConsoleOutputCP"的备注部分说*但是,如果当前字体是光栅字体,SetConsoleOutputCP不会影响扩展字符的显示方式.*您当前的控制台字体是什么?如果您尝试将其更改为Lucida Console或Consolas怎么办?另外,你有没有尝试在启动程序之前运行`chcp 65001`?我不是建议它解决你的问题,只是想知道它是否会改变. (2认同)