為了解決程序對中文支持不好的問題(如路徑不能含中文),強制程序內的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);
其它
擴展閱讀: