UTF8与STL中的宽字符转换

时间:2020-03-06 14:52:17  来源:igfitidea点击:

是否可以以平台无关的方式将std :: string中的UTF8字符串转换为std :: wstring,反之亦然?在Windows应用程序中,我将使用MultiByteToWideChar和WideCharToMultiByte。但是,代码是为多个操作系统编译的,我仅限于标准C ++库。

解决方案

我们可以使用codecvt语言环境方面。有一个特定的特殊定义," codecvt <wchar_t,char,mbstate_t>"可能对我们有用,尽管这种行为是系统特定的,并且不保证以任何方式转换为UTF-8.

我认为没有做到这一点的便携式方法。 C ++不知道其多字节字符的编码。

正如克里斯建议的那样,我们最好的选择是使用编解码器。

我们可以从Boost序列化库中提取utf8_codecvt_facet

其用法示例:

typedef wchar_t ucs4_t;

  std::locale old_locale;
  std::locale utf8_locale(old_locale,new utf8_codecvt_facet<ucs4_t>);

  // Set a New global locale
  std::locale::global(utf8_locale);

  // Send the UCS-4 data out, converting to UTF-8
  {
    std::wofstream ofs("data.ucd");
    ofs.imbue(utf8_locale);
    std::copy(ucs4_data.begin(),ucs4_data.end(),
          std::ostream_iterator<ucs4_t,ucs4_t>(ofs));
  }

  // Read the UTF-8 data back in, converting to UCS-4 on the way in
  std::vector<ucs4_t> from_file;
  {
    std::wifstream ifs("data.ucd");
    ifs.imbue(utf8_locale);
    ucs4_t item = 0;
    while (ifs >> item) from_file.push_back(item);
  }

在增强源中查找" utf8_codecvt_facet.hpp"和" utf8_codecvt_facet.cpp"文件。

有几种方法可以做到这一点,但是结果取决于stringwstring变量中字符编码的含义。

如果我们知道string是ASCII,则可以简单地使用wstring的迭代器构造函数:

string s = "This is surely ASCII.";
wstring w(s.begin(), s.end());

但是,如果string具有其他编码,我们将得到非常糟糕的结果。如果编码是Unicode,则可以看一下ICU项目,该项目提供了一套跨平台的库,这些库可以在各种Unicode编码之间进行转换。

如果`string'在代码页中包含字符,则$ DEITY可能会发怒。

问题定义明确指出8位字符编码为UTF-8. 这使这成为一个琐碎的问题;从一个UTF规范转换到另一个UTF规范,所需要做的只是花点时间。

只需查看这些Wikipedia页面上的UTF-8,UTF-16和UTF-32编码即可。

原理很简单,根据一个UTF规范通过输入并组装一个32位Unicode代码点,然后根据另一个规范发出代码点。各个代码点不需要翻译,就象任何其他字符编码一样。这就是使这成为一个简单问题的原因。

这是将wchar_t转换为UTF-8的快速实现,反之亦然。假定输入已经正确编码,这就是俗语"垃圾进,垃圾出"在这里适用的情况。我认为最好将编码验证作为一个单独的步骤来完成。

std::string wchar_to_UTF8(const wchar_t * in)
{
    std::string out;
    unsigned int codepoint = 0;
    for (in;  *in != 0;  ++in)
    {
        if (*in >= 0xd800 && *in <= 0xdbff)
            codepoint = ((*in - 0xd800) << 10) + 0x10000;
        else
        {
            if (*in >= 0xdc00 && *in <= 0xdfff)
                codepoint |= *in - 0xdc00;
            else
                codepoint = *in;

            if (codepoint <= 0x7f)
                out.append(1, static_cast<char>(codepoint));
            else if (codepoint <= 0x7ff)
            {
                out.append(1, static_cast<char>(0xc0 | ((codepoint >> 6) & 0x1f)));
                out.append(1, static_cast<char>(0x80 | (codepoint & 0x3f)));
            }
            else if (codepoint <= 0xffff)
            {
                out.append(1, static_cast<char>(0xe0 | ((codepoint >> 12) & 0x0f)));
                out.append(1, static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f)));
                out.append(1, static_cast<char>(0x80 | (codepoint & 0x3f)));
            }
            else
            {
                out.append(1, static_cast<char>(0xf0 | ((codepoint >> 18) & 0x07)));
                out.append(1, static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f)));
                out.append(1, static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f)));
                out.append(1, static_cast<char>(0x80 | (codepoint & 0x3f)));
            }
            codepoint = 0;
        }
    }
    return out;
}

上面的代码适用于UTF-16和UTF-32输入,仅因为范围从d800到dfff是无效的代码点。它们表示我们正在解码UTF-16. 如果我们知道wchar_t是32位,则可以删除一些代码以优化该功能。

std::wstring UTF8_to_wchar(const char * in)
{
    std::wstring out;
    unsigned int codepoint;
    while (*in != 0)
    {
        unsigned char ch = static_cast<unsigned char>(*in);
        if (ch <= 0x7f)
            codepoint = ch;
        else if (ch <= 0xbf)
            codepoint = (codepoint << 6) | (ch & 0x3f);
        else if (ch <= 0xdf)
            codepoint = ch & 0x1f;
        else if (ch <= 0xef)
            codepoint = ch & 0x0f;
        else
            codepoint = ch & 0x07;
        ++in;
        if (((*in & 0xc0) != 0x80) && (codepoint <= 0x10ffff))
        {
            if (sizeof(wchar_t) > 2)
                out.append(1, static_cast<wchar_t>(codepoint));
            else if (codepoint > 0xffff)
            {
                out.append(1, static_cast<wchar_t>(0xd800 + (codepoint >> 10)));
                out.append(1, static_cast<wchar_t>(0xdc00 + (codepoint & 0x03ff)));
            }
            else if (codepoint < 0xd800 || codepoint >= 0xe000)
                out.append(1, static_cast<wchar_t>(codepoint));
        }
    }
    return out;
}

同样,如果我们知道wchar_t是32位,则可以从该函数中删除一些代码,但是在这种情况下,它不会有任何区别。表达式" sizeof(wchar_t)> 2"在编译时是已知的,因此任何合适的编译器都将识别无效代码并将其删除。

UTF8-CPP:以可移植方式使用C ++的UTF-8

ConvertUTF.h
ConvertUTF.c

感谢bames53提供更新版本