为了解决程序对中文支持不好的问题(如路径不能含中文),强制程序内的char*字符串采用utf8编码。

编码不统一导致的乱码问题

因为历史原因,C/C++语言中char*字符串可能采用各种编码:ASCII, Latin, utf8, GBK...

所有文本文件可用的编码,char*字符串都可以采用。

 

1 VSCode支持几十种文本编码

 

如果代码中混用两种编码AB,导致采用A编码的char*字符串被用B编码解析,得到的字符串就可能出现乱码。

一般纯ASCII字符不会发生乱码,因为绝大部分编码的0-127部分的字符都和ASCII编码兼容。

当我们代码里有非ASCII字符的编码时,统一编码就显得非常有必要。

 

char*编码要求采用UTF8

目前最通用的char*编码为utf8编码,utf8编码被PythonLinux等设置为默认编码。

Windows在不同语言的版本中,char*的默认编码是当前国家的特定编码,比如中文版采用GBK编码。

Windows内核使用的编码为utf16编码的wchar_t*字符串,调用Windows API传入的char*字符串会被先转换为wchar_t*字符串后再调用Unicode版本的API

 

GBK编码存在不能表示某些字符的问题,utf8可表示所有字符。

 

为了解决编码混乱问题,C++20专门为utf8编码新增了std::u8stringchar8_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

 

 

系统调用的处理

系统提供的函数

命令行输出

  1. 可对 printf / wprintf / std::cout 进行封装以便输出utf8字符串
  2. 直接输出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替代宽字符串

 

文件打开

  1. 采用 _wfopen / CreateFileW 等方法,传入wchar_t字符串表示的路径
  2. 采用std::ofstream::open(std::wstring wstr)打开

 

其它系统API

请采用windows apiUnicode版本,即带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);

 

其它

扩展阅读:

  1. 浅谈C/C++编程中的字符编码转换