为了解决程序对中文支持不好的问题(如路径不能含中文),强制程序内的char*字符串采用utf8编码。
编码不统一导致的乱码问题
因为历史原因,C/C++语言中char*字符串可能采用各种编码:ASCII, Latin, utf8, GBK...
所有文本文件可用的编码,char*字符串都可以采用。
图1 VSCode支持几十种文本编码
如果代码中混用两种编码A和B,导致采用A编码的char*字符串被用B编码解析,得到的字符串就可能出现乱码。
一般纯ASCII字符不会发生乱码,因为绝大部分编码的0-127部分的字符都和ASCII编码兼容。
当我们代码里有非ASCII字符的编码时,统一编码就显得非常有必要。
char*编码要求采用UTF8
目前最通用的char*编码为utf8编码,utf8编码被Python、Linux等设置为默认编码。
Windows在不同语言的版本中,char*的默认编码是当前国家的特定编码,比如中文版采用GBK编码。
Windows内核使用的编码为utf16编码的wchar_t*字符串,调用Windows API传入的char*字符串会被先转换为wchar_t*字符串后再调用Unicode版本的API。
GBK编码存在不能表示某些字符的问题,utf8可表示所有字符。
为了解决编码混乱问题,C++20专门为utf8编码新增了std::u8string和char8_t类型。
很多第三方库也支持utf8编码(当然还有部分不支持)
综上
- 后续产品中的自有C++代码的char*字符串采用utf8编码
- Windows API只使用Unicode版本
- 在遇到不支持utf8编码的库时,对其进行接口封装(编码转换)
- 建议函数的形参添加_u8后缀,以便调用者快速确认编码要求
字符串的编码设置
在定义字符串时,采用以下方法来指定char*字符串为utf8编码
// 在VS2010及之后版本,支持设置字符串为UTF8编码
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
// 在VS2015及之后版本,支持配置编译选项:/execution-charset:utf-8
// 等同项目中所有cpp文件包含上述指令
// 默认str1为GBK编码字符串,进行以上的设置后,str1为utf8编码
const char* str1 = "中文字符串";
// 在VS2015后的版本,只需为字面量添加u8前缀即可得到utf8编码的字符串
// 但Qt的翻译更新功能不认u8前缀,导致ts文件内不包含tr(u8"abc")形式的字符串
const char* str2 = u8"中文字符串";
// Linux系统默认采用utf8编码,无需做额外工作
采用VS2015及之后版本开发的产品中,建议设置编译选项:/execution-charset:utf-8
系统调用的处理
系统提供的函数
命令行输出
- 可对 printf / wprintf / std::cout 进行封装以便输出utf8字符串
- 直接输出std::wstring
// 程序启动时,需要初始化Locale以便输出宽字符
setlocale(LC_ALL, "");
// 输出std::wstring至命令行
std::wstring wstr = L"中文字符串";
std::wcout << wstr;
printf("%ls", wstr.c_str()); // %hs替代窄字符串,%ls替代宽字符串
// printf中%s替代窄字符串,wprintf中%s替代宽字符串
文件打开
- 采用 _wfopen / CreateFileW 等方法,传入wchar_t字符串表示的路径
- 采用std::ofstream::open(std::wstring wstr)打开
其它系统API
请采用windows api的Unicode版本,即带W后缀的版本。utf8字符串可通过编码转换函数转换后传入。
CreateFileA(); // 需传入ansi字符串,打开其它语言文件名的文件可能失败,不允许使用
CreateFileW(); // 传入wchar_t字符串,推荐使用
编码转换方法
产品应该在底层统一提供wchar_t字符串与char字符串的转换方法,供自有代码调用。
- 不允许自有代码对底层编码转换方法的调用
- WideCharToMultiByte / MultiByteToWideChar
- wcstombs / mbstowcs
- 严格限制自有代码对第三方库提供的转换代码的调用
- 对第三方库本身进行更改或扩展时,可调用库提供的转换函数
- 基于Qt的代码可调用QString.toUtf8() / QString.toStdString()
- 严格限制对ANSI字符串转换函数的调用,包含:(代码需注释调用原因)
- string2wstring_ansi / wstring2string_ansi
- QString.toLocal8Bit / QString.toLatin1
- QString::fromLocal8Bit / QString::fromLatin1
- QTextCodec::fromUnicode
底层统一提供的转换方法实现参考:(仅限产品底层进行实现,上层直接调用即可)
std::string wstring2string(const std::wstring& wstr, UINT CodePage)
{
std::string ret;
int len = WideCharToMultiByte(CodePage, 0, wstr.c_str(), (int)wstr.size(), NULL, 0, NULL, NULL);
ret.resize((size_t)len, 0);
WideCharToMultiByte(CodePage, 0, wstr.c_str(), (int)wstr.size(), &ret[0], len, NULL, NULL);
return ret;
}
std::string wstring2string_ansi(const std::wstring& wstr)
{
return wstring2string(wstr, CP_ACP);
}
std::string wstring2string_utf8(const std::wstring& wstr)
{
return wstring2string(wstr, CP_UTF8);
}
std::wstring string2wstring(const std::string& str, UINT CodePage)
{
std::wstring ret;
int len = MultiByteToWideChar(CodePage, 0, str.c_str(), (int)str.size(), NULL, 0);
ret.resize((size_t)len, 0);
MultiByteToWideChar(CodePage, 0, str.c_str(), (int)str.size(), &ret[0], len);
return ret;
}
std::wstring string2wstring_ansi(const std::string& str_ansi)
{
return string2wstring(str_ansi, CP_ACP);
}
std::wstring string2wstring_utf8(const std::string& str_u8)
{
return string2wstring(str_u8, CP_UTF8);
}
代码示例
// QString字符串
QString qstr;
std::string str = qstr.toStdString();
std::wstring wstr = qstr.toStdWString();
String unig_str = String(qstr.toUtf8());
// 如果需要在后面使用const char*字符串,一定要显式定义包含数据的变量
auto tmp_str = qstr.toUtf8();
const char* pstr = tmp_str.constData();
func_use_const_char_str(pstr);
// 或者下面这种形式,编译器会自动转换成const char*
func_use_const_char_str(qstr.utf8());
// 变参函数,必须显式调用constData()转换为const char*,因为编译器不会自动转换
sprintf(buf, "xxx%sxxx", qstr.toUtf8().constData());
// std::string字符串(utf8编码)
std::string str;
std::wstring = string2wstring_utf8(str);
QString qstr = QString::fromStdString(str);
String unig_str = String(str.data());
const char* pstr = str.data();
// std::wstring
std::wstring wstr;
std::string str = wstring2string_utf8(wstr);
QString qstr = QString::fromStdWString(wstr);
String unig_str = String(wstring2string_utf8(wstr).data());
// const char*(utf8编码)
const char* pstr;
std::string str = pstr;
std::wstring wstr = string2wstring_utf8(pstr);
QString qstr = QString::fromUtf8(pstr);
String unig_str = String(pstr);
// const wchar_t*
const wchar_t* pwstr;
std::string str = wstring2string_utf8(pwstr);
std::wstring wstr = pwstr;
QString qstr = QString::fromWCharArray(pwstr);
String unig_str = String(wstring2string_utf8(pwstr).data);
其它
扩展阅读: