根本原因是控制台ANSI编码(如GBK)与std::wcout的UTF-16预期不匹配;须同时满足:_setmode(_fileno(stdout), _O_U16TEXT)、SetConsoleOutputCP(CP_UTF8)、源文件保存为UTF-8 with BOM。

Windows 下 std::wcout 输出中文显示为问号或方块
根本原因是控制台默认使用本地 ANSI 编码(如 GBK),而 std::wcout 期望 UTF-16,两者不匹配。不改终端编码、不设 locale,光靠 setlocale(LC_ALL, "") 或 _setmode(_fileno(stdout), _O_U16TEXT) 单独用都不够。
必须同时满足三个条件:
-
_setmode(_fileno(stdout), _O_U16TEXT)—— 让 stdout 接收宽字符流 -
SetConsoleOutputCP(CP_UTF8)或SetConsoleOutputCP(65001)—— 显式设控制台输出编码为 UTF-8(注意:这和_O_U16TEXT并不冲突,Win10+ 支持 UTF-8 模式下的宽字符重定向) - 源文件本身保存为 UTF-8 with BOM(VS 默认会加,但 VS Code 默认不加;若无 BOM,
L"你好"可能被误读为 GBK 字节序列)
常见错误现象:std::wcout 输出乱码或空;<code>std::cout 在非 UTF-8 终端里照样乱码。
跨平台读取 UTF-8 文件并转成 std::wstring(Linux/macOS/Windows)
标准库没有直接的 UTF-8 ↔ UTF-16 转换接口,std::codecvt_utf8<wchar_t></wchar_t> 已被 C++17 标记为 deprecated,且在 GCC 11+ 和 Clang 中默认不可用。
立即学习“C++免费学习笔记(深入)”;
推荐方案是绕过 std::codecvt,用轻量级转换函数:
- Windows:调用
MultiByteToWideChar(CP_UTF8, ...)+WideCharToMultiByte(CP_UTF8, ...) - Linux/macOS:用
iconv()(需链接-liconv)或更简单的std::mbstowcs/std::wcstombs—— 但注意它们依赖当前LC_CTYPE,必须先setlocale(LC_CTYPE, "en_US.UTF-8")或类似 UTF-8 locale
示例(Windows):
std::string read_utf8_file(const char* path) {
std::ifstream f(path, std::ios::binary);
return std::string(std::istreambuf_iterator<char>(f), {});
}
std::wstring utf8_to_wstring(const std::string& s) {
if (s.empty()) return {};
int len = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0);
std::wstring ws(len, 0);
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &ws[0], len);
return ws;
}
std::string 存的是 UTF-8,但 str.length() 不等于字符数
这是 UTF-8 的本质特性:ASCII 字符占 1 字节,中文通常占 3 字节。str.length() 返回字节数,不是 Unicode 码点数,更不是“人眼看到的字数”。
容易踩的坑:
- 用
str.substr(0, 5)截取前 5 个字节,可能切在某个中文字符中间,导致后续解码失败 - 循环
for (size_t i = 0; i 遍历时,<code>i是字节偏移,不是字符索引 - 正则匹配、
std::string::find等操作在 UTF-8 上仍可工作(因为它们只认字节),但语义上不安全
真正需要按字符遍历时,得手动解析 UTF-8:检查首字节高位模式(0xxxxxxx、110xxxxx、1110xxxx…),跳过后续字节。别指望 std::string 自带“Unicode-aware”方法。
CMake 项目中确保源码和编译器都按 UTF-8 处理
即使代码写了 u8"中文",如果编译器没被告知源文件编码,依然会出错。MSVC 默认按系统 locale 解析源码;GCC/Clang 默认按 UTF-8,但若文件含 BOM 或声明了 #pragma execution_character_set("utf-8"),行为会变。
关键配置项:
- MSVC:在
CMakeLists.txt中加add_compile_options(/source-charset:utf-8 /execution-charset:utf-8) - Clang/GCC:加
add_compile_options(-finput-charset=utf-8 -fexec-charset=utf-8)(GCC 10+ 支持;旧版仅支持-finput-charset) - 统一要求:所有
.cpp文件保存为 UTF-8 without BOM(BOM 会导致 MSVC 报warning C4819,且某些头文件包含逻辑异常)
漏掉任一环,比如 CMake 没设 /source-charset,而源码又是 GBK 编码,u8"中文" 就会被当 GBK 字节硬塞进 UTF-8 字符串,运行时全乱。
最麻烦的其实是混合场景:既有第三方库用 char* 做内部字符串,又得和 Windows API 的 LPCWSTR 交互;这时候转换点分散、编码假设不一致,比单点乱码更难定位。盯住每个输入来源的原始编码、每次转换的意图、每处输出的目标环境——少一个环节,就多一个问号。










