UTL8与STL中的宽字符转换


76

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


3
顺便说一句,标准C ++库不称为STL。STL只是标准C ++库的一小部分。在这种情况下,我相信您正在要求标准C ++库中的功能,并且我已经做出了相应的回答。
克里斯·杰斯特·杨

6
您尚未指定最终要使用的编码。wstring没有指定任何特定的编码。当然,在wchar_t为4个字节宽的平台上转换为utf32,如果wchar_t为2个字节则转换为utf16是很自然的。那是你要的吗?
杰夫

1
@jalf您的评论具有误导性。std::wstringstd::basic_string<wchar_t>wchar_t是表示Unicode字符的不透明数据类型(在Windows中只有16位长,这意味着Windows不遵循该标准)。抽象的Unicode字符没有“编码”,它们只是字符。
kirelagin

Answers:


53

我五年前问过这个问题。那时,该线程对我非常有帮助,我得出了一个结论,然后继续进行我的项目。有趣的是,我最近需要类似的东西,与过去的那个项目完全无关。在研究可能的解决方案时,我偶然发现了自己的问题:)

我现在选择的解决方案基于C ++ 11。康斯坦丁在回答中提到的Boost库现在已成为标准的一部分。如果将std :: wstring替换为新的字符串类型std :: u16string,则转换将如下所示:

UTF-8至UTF-16

std::string source;
...
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::u16string dest = convert.from_bytes(source);    

UTF-16至UTF-8

std::u16string source;
...
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::string dest = convert.to_bytes(source);    

从其他答案可以看出,有多种解决方法。这就是为什么我不选择可接受的答案。


wstring表示2或4个字节,而不是单字节字符。从utf8编码切换到哪里?
Chawathe Vipul S 2013年

1
我的codecvt性能有些奇怪,请查看此处了解详细信息:stackoverflow.com/questions/26196686/…–
Xtra Coder

2
这是带有LE或BE的UTF-16吗?
thomthom

7
std :: wstring_convert在C ++ 17中弃用
HojjatJafary

1
@HojjatJafary,替换的是什么?
雅加尔


23

您可以utf8_codecvt_facetBoost序列化库中提取。

其用法示例:

  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.hpputf8_codecvt_facet.cpp文件升压来源。


我虽然您必须在流打开之前对其进行注入,否则注入将被忽略!
马丁·约克

马丁,似乎可以在Visual Studio 2005中使用:0x41a已成功转换为{0xd0,0x9a} UTF-8序列。
君士坦丁

22

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

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

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

这是wchar_tUTF-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在编译时是已知的,因此任何体面的编译器都将识别无效代码并将其删除。


我看不到他在原始问题中对包含UTF-8编码字符串的std :: string进行了任何设置:“是否可以以独立于平台的方式将std :: string转换为std :: wstring,反之亦然?”
Nemanja Trifunovic

1
帖子标题中指定了UTF-8。您是正确的,它从文本的正文中丢失。
Mark Ransom

6
但是“ widechar”并不一定意味着UTF16
moogs

6
您所拥有的可能是一个很好的“概念证明”。成功转换有效的编码是一回事。根据规范正确处理无效编码数据(例如,UTF-16中的未配对替代)的转换是另一种努力。为此,您确实需要一些经过更彻底设计和测试的代码。
Craig McQueen

2
@克雷格·麦奎因(Craig McQueen),你是绝对正确的。我假设编码已经正确,而且只是机械转换。我敢肯定,在某些情况下,这种代码就足够了-但应明确说明其局限性。从最初的问题尚不清楚这是否值得关注。
Mark Ransom

13

有几种方法可以执行此操作,但是结果取决于stringandwstring变量中的字符编码。

如果您知道stringASCII,则可以使用wstring的迭代器构造函数:

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

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

如果您string的代码页中包含字符,则$ DEITY可能会怜悯您。


4
ICU也会从我遇到的每个字符编码中进行转换。很大。
马丁·约克


2

您可以使用codecvt语言环境方面。定义了一个特定的专业化名称,codecvt<wchar_t, char, mbstate_t>您可能会用到,尽管它的行为是特定于系统的,并且不保证以任何方式转换为UTF-8。


2
根据语言环境进行编码/解码是一个坏主意。就像您说的:“不保证”。
泰勒·朗

@TylerLong显然应该为所需的转换专门配置std :: locale实例。
Basilevs 2014年

@Basilevs我仍然认为使用语言环境进行编码/解码是错误的。正确的方法是配置encoding而不是locale。据我所知,没有可以代表每个unicode字符的语言环境。假设我要编码一个包含所有Unicode字符的字符串,您建议我配置哪种语言环境?如果我错了,请给我留言。
泰勒·隆

C ++中的@TylerLong语言环境是一个非常抽象的概念,它涵盖的内容远不止区域设置和编码。基本上,它可以做到。虽然codecvt_facet确实处理的不仅仅是简单的重新编码,但绝对没有什么可以阻止它进行简单的unicode转换。
Basilevs 2014年


0

为utf-8到utf-16 / utf-32转换创建了我自己的库-但为此目的决定创建现有项目的分支。

https://github.com/tapika/cutf

(源自https://github.com/noct/cutf

API可与普通C以及C ++一起使用。

函数原型如下所示:(有关完整列表,请参见https://github.com/tapika/cutf/blob/master/cutf.h

//
//  Converts utf-8 string to wide version.
//
//  returns target string length.
//
size_t utf8towchar(const char* s, size_t inSize, wchar_t* out, size_t bufSize);

//
//  Converts wide string to utf-8 string.
//
//  returns filled buffer length (not string length)
//
size_t wchartoutf8(const wchar_t* s, size_t inSize, char* out, size_t outsize);

#ifdef __cplusplus

std::wstring utf8towide(const char* s);
std::wstring utf8towide(const std::string& s);
std::string  widetoutf8(const wchar_t* ws);
std::string  widetoutf8(const std::wstring& ws);

#endif

样本用法/用于utf转换测试的简单测试应用程序:

#include "cutf.h"

#define ok(statement)                                       \
    if( !(statement) )                                      \
    {                                                       \
        printf("Failed statement: %s\n", #statement);       \
        r = 1;                                              \
    }

int simpleStringTest()
{
    const wchar_t* chineseText = L"主体";
    auto s = widetoutf8(chineseText);
    size_t r = 0;

    printf("simple string test:  ");

    ok( s.length() == 6 );
    uint8_t utf8_array[] = { 0xE4, 0xB8, 0xBB, 0xE4, 0xBD, 0x93 };

    for(int i = 0; i < 6; i++)
        ok(((uint8_t)s[i]) == utf8_array[i]);

    auto ws = utf8towide(s);
    ok(ws.length() == 2);
    ok(ws == chineseText);

    if( r == 0 )
        printf("ok.\n");

    return (int)r;
}

如果此库不能满足您的需求,请随时打开以下链接:

http://utf8everywhere.org/

并向下滚动至页面末尾,然后选择您喜欢的任何较重的库。


-1

我认为没有可移植的方法可以做到这一点。C ++不知道其多字节字符的编码。

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


这个问题说“ UTF8”,所以“其多字节字符的编码”是已知的。
泰勒·朗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.