将utf16宽std :: wstring转换为utf8窄std :: string以获得稀有字符时的问题

aat*_*two 1 c++ string unicode utf-8 character-encoding

为什么使用utf16编码的宽字符串转换为utf8编码的窄字符串时,会转换为使用此常见转换函数转换时似乎不正确的十六进制值?

std::string convert_string(const std::wstring& str)
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
    return conv.to_bytes(str);
}
Run Code Online (Sandbox Code Playgroud)

你好。我在Windows上有一个C ++应用,该应用在命令行上需要一些用户输入。我正在使用宽字符主入口点来获取输入为utf16字符串,并使用上述函数将其转换为utf8窄字符串。

该功能可以在网上的许多地方找到,并且几乎可以在所有情况下使用。但是,我发现了一些无法正常运行的示例。

例如,如果我输入emojii字符“”作为字符串文字(在我的utf8编码的cpp文件中)并将其写入磁盘,则文件(FILE-1)包含以下数据(这是此处指定的正确utf8十六进制值)https ://www.fileformat.info/info/unicode/char/1f922/index.htm):

    0xF0 0x9F 0xA4 0xA2
Run Code Online (Sandbox Code Playgroud)

但是,如果我在命令行上将emojii传递给我的应用程序,并使用上面的转换函数将其转换为utf8字符串,然后将其写入磁盘,则文件(FILE-2)包含不同的原始字节:

    0xED 0xA0 0xBE 0xED 0xB4 0xA2
Run Code Online (Sandbox Code Playgroud)

尽管第二个文件似乎表明如果您复制并粘贴十六进制值(至少在notepad ++中),则转换产生了错误的输出,但它产生了正确的emojii。WinMerge还将两个文件视为相同。

最后我很想知道以下几点:

  1. 在上面的示例中,看起来错误的转换后的十六进制值如何正确映射到正确的utf8字符
  2. 为什么转换功能会将某些字符转换为这种形式,而几乎所有其他字符都产生预期的原始字节
  3. 另外,我想知道是否可以修改转换函数以阻止它以这种形式输出这些稀有字符

我应该注意,我下面已经有一个变通方法,该函数使用WinAPI调用,但是仅使用标准库调用才是梦想:)

std::string convert_string(const std::wstring& wstr)
{
    if(wstr.empty())
        return std::string();

    int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
    std::string strTo(size_needed, 0);
    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
    return strTo;
}
Run Code Online (Sandbox Code Playgroud)

Stu*_*Stu 5

问题是std::wstring_convert<std::codecvt_utf8<wchar_t>>从UCS-2转换,而不是从UTF-16转换。BMP内部的字符(U + 0000..U + FFFF)在UCS-2和UTF-16中具有相同的编码,因此可以使用,但是BMP外部的字符(U + FFFF..U + 10FFFF),例如作为您的表情符号,UCS-2中根本不存在。这意味着转换无法理解该字符,并且会产生不正确的UTF-8字节(从技术上讲,它已将UTF-16代理对的每半转换为单独的UTF-8字符)。

您需要std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>改用。