除windows平台外大部分其他平台,编译器默认使用的编码都是UTF-8编码,最新版本的Clang编译器只支持UTF-8编码。如果程序需要在多个平台编译运行,则代码必须使用UTF-8。使用UTF-8可以更容易的在多字节字符串(char, std::string)和宽字符(wchar_t std::wstring)直接转换,更容易避免程序乱码,中文路径错误等问题。
Windows上MSVC默认使用的编码是当前系统设置的编码,中文系统默认是GBK。Windows上可以在【控制面板\时钟和区域\区域\管理】中设置默认编码,如下图:
?中文系统默认设置为“中文(简体, 中文)”,编码为GBK。上图中如果勾选红框中的选项,则默认编码会设为UTF-8。默认编码一般不要修改,如果默认编码设置错了,在非Unicode程序中会出现乱码。
C++代码字符编码
MSVC中C++代码字符编码默认与系统一致,中文系统默认是GBK。有两种方法修改为UTF-8编码:
-
直接使用UTF-8带BOM的文件格式。 -
使用UTF-8(不带BOM)格式并使用编译选项 /source-charset:utf-8 。cmake设置方法:
# 必须在add_library , add_executable 前设置,否则无效
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/source-charset:utf-8>")
使用了UTF-8编码的代码,程序字符串变量并不就使用UTF-8编码,默认MSVC在编译时会将UTF-8编码的字符串转换成程序运行时编码,中文系统为GBK,也就是字符串变量存放的是GBK编码的字符串。
C++运行时字符编码
MSVC中C++运行时字符编码默认与系统一致,中文系统默认是GBK。也有两种方法修改为UTF-8:
-
使用字符串字面量,在字符串前加上u8,
char* s1 = u8"abc中文"; std::string s2(u8"abc中文");
?????2. 可以通过使用编译选项 /execution-charset:utf-8 修改为UTF-8, cmake设置方法:
# 必须在add_library , add_executable 前设置,否则无效
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/execution-charset:utf-8>")
这样代码在编译时会将字符串转换为UTF-8,运行时字符串变量中的字符则是UTF-8编码。
控制台显示UTF-8编码
windows控制台字符编码使用的是系统默认的编码,可以通过chcp 命令查看。如果控制台的编码不是UTF-8(对应编码:65001),显示UTF-8字符串会出现乱码,可通过chcp 65001 命令修改控制台的编码,从而正常显示UTF-8字符串。
更好的方法是修改程序的locale变量,这个会修改C/C++的运行环境,代码如下:
td::locale::global(std::locale(".utf8"));
// 或者
// std::setlocale(LC_ALL, ".UTF-8");
因为修改locale变量修改的是C/C++的运行环境,并不会修改Windows系统函数的环境,所以如果调用系统函数WriteConsoleOutput 函数,还是会出现乱码。 Windows上有些库(例如:spdlog)会调用WriteConsoleOutput 函数,设置的locale还是会出现乱码,还需要设置控制台编码,如下:
std::locale::global(std::locale(".utf8"));
SetConsoleOutputCP(CP_UTF8);
使用/utf-8
使用编译选项/utf-8 可以同时将代码的编码和程序运行时编码都设置为UTF-8。
参考:https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
不要混用字符编码与编译选项
-
如果使用的是UTF-8编码格式的代码,需要加上/source-charset:utf-8 或/utf-8 编译选项。 -
如果不是UTF-8编码格式的代码,一定不要加/source-charset:utf-8 或/utf-8 编译选项。
具体原因:
如果使用的是UTF-8带BOM的格式的代码,MSVC可以自动识别代码为UTF-8格式。但是因为UTF-8 BOM是Windows的标准其他平台并不认,所以一般使用UTF-8(不带BOM)的格式。如果代码不带BOM,不管当前代码是不是UTF-8,MSVC都会将代码当作当前系统默认的字符编码,编译时不会转码。程序运行时字符变量的内容是UTF-8编码,似乎没问题??但如果此时使用字符串字面量,在字符串前加上u8则会出现问题,编译把一个UTF-8的字符串当作GBK,然后转换成UTF-8,此时必然出现乱码。
Visual Studio保存UTF-8文件时会自动增加BOM,如果不想VS自动添加BOM可以使用editorconfig文件配置。在项目根目录下保存一个文件名为: .editorconfig 的文件,文件内容如下:
# editorconfig.org
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
文件路径
C++中文件保存读取以及C++17增加的filesystem中的char/string使用的是系统默认编码,中文系统默认是GBK。可以通过std::locale::global(std::locale(".utf8")) 修改为UTF-8。设置后传入std::fstream和filesystem的字符串参数必须是UTF-8编码,filesystem返回的字符编码也是了UTF-8。
Windows系统函数
通过设置std::locale::global(std::locale(".utf8")) 并不会改变Windows系统函数多字节版本的字符编码,Windows系统函数多字节版本依然是根据系统设置的默认编码决定。
如果使用UTF-8编码则涉及字符串的Windows系统函数全部使用宽字符(UTF-16)版本,例如创建文件时使用CreateFileW ,而不是 CreateFileA 。毕竟UTF-8编码和宽字节字符都是Unicode字符,转换非常方便,也很容易实现。
命令行参数
对于int main(int argc, char* args[]) 接受的的参数args,其编码始终是系统默认编码(中文为GBK),即使通过chcp修改控制台的默认编码其接受到的产生依然是系统默认编码。
除了main函数外,还有另外一个函数 wmain,int wmain(int argc, wchar_t* args[]) 这个函数args 是宽字符(UTF-16),可以很容易的转换为UTF-8。
对于窗口程序的主函数winMain,也有对应的wWinMain
参考:https://stackoverflow.com/questions/13871617/winmain-and-main-in-c-extended
Windows系统函数使用UTF-8字符串
前面说了程序中Windows系统函数字符串编码默认使用的是根据系统设定的,但实际上是可以通过使用manifest文件修改的。修改后不光系统函数使用UTF-8编码,主函数的命令行参数也是UTF-8编码。
创建一个文件名为:manifest.manifest 的文件,写入以下内容:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity type="win32" name="..." version="6.0.0.0"/>
<application>
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
</assembly>
将文件 manifest.manifest 加入到编译中,VS设置如下:
Windows上CMake可以识别 *.manifest 文件,只需把manifest文件和源码一样加入到程序中就行,如下:
add_executable(CppTest main.cpp manifest.manifest)
注意这种方法只对Win 10 1903(2019年5月发布)后的版本才有效。
参考:https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page
获取系统编码
通过函数GetACP() (ACP->Active Code Page)可以在程序中获取当前系统的编码,其返回值是一个整数。整数对应的编码可以在这里查到https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers。
获取C++ locale 的编码,可以先调用setlocale(LC_ALL, "") ,后面调用 setlocale(LC_ALL, NULL) ,就会返回当前系统的locale字符串。
setlocale(LC_ALL, "");
std::cout << "LC_ALL: " << setlocale(LC_ALL, NULL) << std::endl;
std::cout << "LC_CTYPE: " << setlocale(LC_CTYPE, NULL) << std::endl;
输出:
参考:https://stackoverflow.com/questions/12170488/how-to-get-current-locale-of-my-environment
字符转换
MultiByteToWideChar 和WideCharToMultiByte 可以在Unicode和非Unicode直接转换。这两个函数中codepag参数可以使用CP_ACP (0),代表当前系统的编码。
总结
Window上MVC使用UTF-8编码:
-
代码的格式使用UTF-8(无BOM) 格式 -
编译器中设置编译选项 add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>") -
程序运行时设置local变量和控制台编码 std::locale::global(std::locale(".utf8")); SetConsoleOutputCP(CP_UTF8); -
Windows系统函数的处理有两种方法
-
Windows系统函数使用宽字符版本,将UTF-8转化为UTF-16,或者UTF-16转为UTF-8。 -
使用manifest文件将系统函数修改为使用UTF-8编码。
-
使用wmain /wWinMain 获取命令行参数,或使用main /WinMain 并用 manifest文件修改为UTF-8编码。使用manifest文件时需要注意有些系统不支持,此时可以通过函数GetACP 查询当前编码,如果不是的情况可以使用MultiByteToWideChar 转换为Unicode编码。
|