[英]Can std::cout work with UTF-8 on Windows?
我想让std::cout
打印一个UTF-8文字。 对于gcc来说,这似乎是一件容易的事,而对于Windows,这是极其困难的。
我试图开始工作的代码是:
std::cout << "Ελληνικά Русский 你好";
环境:
要求:
main()
的开头 我试过的
#pragma execution_character_set("utf-8")
SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8);
Use Unicode character set
没有任何帮助,没有StackOverflow答案可以解决问题。
编辑
要使Unicode 部分正常工作,请执行以下操作:
initStreams()
从下面的清单中调用initStreams()
Use Unicode Character Set
” /utf-8
选项 无法运作:
wprintf
cin
/ wcin
initStreams()
实现:
#include <cassert> // assert
#include <codecvt> // std::codecvt_utf8 (C++11)
#include <stdexcept> // std::exception
#include <streambuf> // std::basic_streambuf
#include <iostream> // std::cout, std::endl
#include <locale> // std::locale
#include <memory> // std::unique_ptr (C++11)
#undef UNICODE
#define UNICODE
#undef STRICT
#define STRING
#include <windows.h> // MultiByteToWideChar
class OutputForwarderBuffer : public std::basic_streambuf<char>
{
public:
using Base = std::basic_streambuf<char>;
using Traits = Base::traits_type;
using StreamBuffer = std::basic_streambuf<char>;
using WideStreamBuffer = std::basic_streambuf<wchar_t>;
using Base::int_type;
using Base::char_type;
OutputForwarderBuffer(
StreamBuffer& existingBuffer,
WideStreamBuffer* pWideStreamBuffer
)
: Base(existingBuffer)
, pWideStreamBuffer_(pWideStreamBuffer)
{
}
OutputForwarderBuffer(OutputForwarderBuffer const&) = delete;
void operator=(OutputForwarderBuffer const&) = delete;
protected:
std::streamsize xsputn(char const* s, std::streamsize n) override
{
if (n == 0) { return 0; }
int const sourceSize = static_cast<int>(n);
int const destinationSize = MultiByteToWideChar(CP_UTF8, 0, s, sourceSize, nullptr, 0);
wideCharBuffer_.resize(static_cast<size_t>(sourceSize));
int const nWideCharacters = MultiByteToWideChar(CP_UTF8, 0, s, sourceSize, &wideCharBuffer_[0], destinationSize);
assert(nWideCharacters > 0 && nWideCharacters == destinationSize);
return pWideStreamBuffer_->sputn(&wideCharBuffer_[0], destinationSize);
}
int_type overflow(int_type c) override
{
bool const cIsEOF = Traits::eq_int_type(c, Traits::eof());
int_type const failureValue = Traits::eof();
int_type const successValue = (cIsEOF ? Traits::not_eof(c) : c);
if (!cIsEOF) {
char_type const ch = Traits::to_char_type(c);
std::streamsize const nCharactersWritten = xsputn(&ch, 1);
return (nCharactersWritten == 1 ? successValue : failureValue);
}
return successValue;
}
private:
WideStreamBuffer* pWideStreamBuffer_;
std::wstring wideCharBuffer_;
};
void setUtf8Conversion(std::basic_ios<wchar_t>& stream)
{
stream.imbue(std::locale(std::locale::empty(), new std::codecvt_utf8_utf16<wchar_t>()));
}
bool isConsole(HANDLE streamHandle)
{
DWORD consoleMode;
return !!GetConsoleMode(streamHandle, &consoleMode);
}
bool isConsole(DWORD stdStreamId)
{
return isConsole(GetStdHandle(stdStreamId));
}
void initStreams()
{
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
setUtf8Conversion(std::wcout);
setUtf8Conversion(std::wcerr);
setUtf8Conversion(std::wclog);
static OutputForwarderBuffer coutBuffer(*std::cout.rdbuf(), std::wcout.rdbuf());
static OutputForwarderBuffer cerrBuffer(*std::cerr.rdbuf(), std::wcerr.rdbuf());
static OutputForwarderBuffer clogBuffer(*std::clog.rdbuf(), std::wclog.rdbuf());
std::cout.rdbuf(&coutBuffer);
std::cerr.rdbuf(&cerrBuffer);
std::clog.rdbuf(&clogBuffer);
}
这是我要做的:
确保您的源文件是utf-8编码的并且具有正确的内容(在另一个编辑器中打开它们,检查字形和文件编码)
从等式中删除控制台-将输出重定向到文件,并使用支持utf-8的编辑器检查其内容(就像使用源代码一样)
在MSVC2015 +中使用/ utf-8 cmdline选项-这将强制编译器将所有源文件视为utf-8编码一次,并且存储在生成的二进制文件中的字符串文字将被utf-8编码。
从等式中删除iostream (不能等到该库死掉,TBH)-使用cstdio
在这一点上输出应该工作(它为我做)
要使控制台输出正常工作-使用SetConsoleOutputCP(CP_UTF8)并使其使用支持Unicode平面的 TrueType字体(我怀疑汉字要在控制台中工作,您需要在系统中安装支持相关Unicode平面的字体,并且您的控制台应配置为使用它)
不确定控制台输入(不必处理),但是我怀疑SetConsoleCP(CP_UTF8)应该使它与非宽域I / O一起使用
放弃使用宽I / O(wcout / etc)的想法-无论如何,您为什么要这样做? Unicode与utf-8编码的char const *一起使用时效果很好
一旦达到此阶段,就可以处理iostream (如果您坚持使用它)。 我暂时不理会wcin / wcout。 如果尚未使用,请尝试使用utf-8语言环境注入相关的cin / cout。
http://utf8everywhere.org/提倡的想法是仅在调用Windows API时才转换为UCS-2。 这使您的OutputForwarderBuffer不再需要。
我猜(如果您真的坚持),现在您可以尝试扩大iostream的工作范围。 祝您好运,我想您将不得不重新配置控制台(这将破坏非宽域I / O),或者以某种方式让您的wcout / wcin即时执行UCS2-to-UTF8转换(并且仅在将其连接到控制台时) 。
编辑:从Windows 10开始,您还需要以下内容:
setvbuf(stderr, NULL, _IOFBF, 1024); // on Windows 10+ we need buffering or console will get 1 byte at a time (screwing up utf-8 encoding)
setvbuf(stdout, NULL, _IOFBF, 1024);
不幸的是,这也意味着如果在下一次刷新之前完全填充缓冲区,仍然有可能搞砸您的输出。 正确的解决方案-在每个字符串发送到输出之后(假定每个字符串小于1024),手动对其进行刷新( endl
或fflush()
)。 如果仅MS支持行缓冲...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.