【问题标题】:Convert between string, u16string & u32string在字符串、u16string 和 u32string 之间转换
【发布时间】:2011-11-06 03:47:09
【问题描述】:

我一直在寻找一种在 Unicode 字符串类型之间进行转换的方法,结果遇到了this method。不仅我没有完全理解方法(没有cmet)而且文章暗示将来会有更好的方法。

如果这是最好的方法,请您指出是什么使它起作用,如果不是,我想听听有关更好方法的建议。

【问题讨论】:

标签: c++ string unicode c++11 unicode-string


【解决方案1】:

mbstowcs()wcstombs() 不一定会转换为 UTF-16 或 UTF-32,它们会转换为 wchar_t 以及任何语言环境 wchar_t 编码。所有 Windows 语言环境都使用 2 字节 wchar_t 和 UTF-16 作为编码,但其他主要平台使用 4 字节 wchar_t 和 UTF-32(甚至某些语言环境的非 Unicode 编码)。仅支持单字节编码的平台甚至可以有一个单字节wchar_t,并且编码因区域设置而异。所以wchar_t 在我看来对于可移植性和 Unicode 来说是一个糟糕的选择。 *

C++11 中引入了一些更好的选项; std::codecvt 的新特化、新的 codecvt 类和一个新模板,使使用它们进行转换非常方便。

首先,使用 codecvt 的新模板类是 std::wstring_convert。创建 std::wstring_convert 类的实例后,您可以轻松地在字符串之间进行转换:

std::wstring_convert<...> convert; // ... filled in with a codecvt to do UTF-8 <-> UTF-16
std::string utf8_string = u8"This string has UTF-8 content";
std::u16string utf16_string = convert.from_bytes(utf8_string);
std::string another_utf8_string = convert.to_bytes(utf16_string);

为了进行不同的转换,您只需要不同的模板参数,其中之一是 codecvt facet。以下是一些易于与 wstring_convert 一起使用的新方面:

std::codecvt_utf8_utf16<char16_t> // converts between UTF-8 <-> UTF-16
std::codecvt_utf8<char32_t> // converts between UTF-8 <-> UTF-32
std::codecvt_utf8<char16_t> // converts between UTF-8 <-> UCS-2 (warning, not UTF-16! Don't bother using this one)

使用这些的示例:

std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::string a = convert.to_bytes(u"This string has UTF-16 content");
std::u16string b = convert.from_bytes(u8"blah blah blah");

新的 std::codecvt 特化有点难用,因为它们有一个受保护的析构函数。为了解决这个问题,您可以定义一个具有析构函数的子类,或者您可以使用 std::use_facet 模板函数来获取现有的 codecvt 实例。此外,这些特化的一个问题是您不能在 Visual Studio 2010 中使用它们,因为模板特化不适用于 typedef 的类型,并且编译器将 char16_t 和 char32_t 定义为 typedef。下面是定义您自己的 codecvt 子类的示例:

template <class internT, class externT, class stateT>
struct codecvt : std::codecvt<internT,externT,stateT>
{ ~codecvt(){} };

std::wstring_convert<codecvt<char16_t,char,std::mbstate_t>,char16_t> convert16;
std::wstring_convert<codecvt<char32_t,char,std::mbstate_t>,char32_t> convert32;

char16_t 特化在 UTF-16 和 UTF-8 之间转换。 char32_t 特化,UTF-32 和 UTF-8。

请注意,C++11 提供的这些新转换不包括任何直接在 UTF-32 和 UTF-16 之间进行转换的方法。相反,您只需组合两个 std::wstring_convert 实例。


***** 我想我会添加一个关于 wchar_t 及其用途的注释,以强调为什么它通常不应该用于 Unicode 或可移植的国际化代码。以下是我的回答https://stackoverflow.com/a/11107667/365496的简短版

什么是 wchar_t?

wchar_t 被定义为可以将任何语言环境的 char 编码转换为 wchar_t,其中每个 wchar_t 恰好代表一个代码点:

类型 wchar_t 是一个独特的类型,其值可以表示支持的语言环境 (22.3.1) 中指定的最大扩展字符集的所有成员的不同代码。 -- [basic.fundamental] 3.9.1/5

要求 wchar_t 足够大以同时表示来自所有语言环境的任何字符。也就是说,用于 wchar_t 的编码可能因地区而异。这意味着您不一定要使用一种语言环境将字符串转换为 wchar_t,然后再使用另一种语言环境转换回 char。

由于这似乎是 wchar_t 在实践中的主要用途,您可能想知道如果不是这样,它有什么用处。

wchar_t 的最初意图和目的是通过定义它来简化文本处理,以便它需要从字符串的代码单元到文本字符的一对一映射,从而允许使用相同的简单算法与 ascii 字符串一起使用其他语言。

不幸的是,对 wchar_t 的要求假设字符和代码点之间的一对一映射来实现这一点。 Unicode 打破了这一假设,因此您也不能安全地将 wchar_t 用于简单的文本算法。

这意味着可移植软件既不能使用 wchar_t 作为语言环境之间文本的通用表示,也不能使用简单的文本算法。

wchar_t 今天有什么用?

不多,反正对于可移植代码。如果定义了__STDC_ISO_10646__,则 wchar_t 的值直接表示在所有语言环境中具有相同值的 Unicode 代码点。这样就可以安全地进行前面提到的跨语言环境转换。但是,您不能仅依靠它来决定可以以这种方式使用 wchar_t,因为尽管大多数 unix 平台都定义了它,但 Windows 并没有,即使 Windows 在所有语言环境中都使用相同的 wchar_t 语言环境。

我认为Windows没有定义__STDC_ISO_10646__的原因是因为Windows使用UTF-16作为它的wchar_t编码,而且因为UTF-16使用代理对来表示大于U+FFFF的码点,也就是说UTF-16不满足__STDC_ISO_10646__的要求。

对于特定于平台的代码,wchar_t 可能更有用。它在 Windows 上基本上是必需的(例如,某些文件根本无法在不使用 wchar_t 文件名的情况下打开),尽管据我所知,Windows 是唯一正确的平台(所以也许我们可以将 wchar_t 视为“Windows_char_t”)。

事后看来,wchar_t 显然对于简化文本处理或作为与区域设置无关的文本的存储没有用。可移植代码不应尝试将其用于这些目的。

【讨论】:

  • 非常感谢您如此深入的回复,这正是我想要的。我可以确认一下 UTF-16 到 UTF-32 需要 UTF-16 到 UTF-8 再到 UTF-32 吗?
  • 是的,你必须通过 UTF-8。
  • 其实可能有办法直接在UTF-16和UTF-32之间走,不过我没用过,所以不太清楚。看看另一个 C++11 方面:codecvt_utf16。
  • 顺便说一句,这些东西在 libc++ 中实现的(还不是 clang 的标准 c++ 库),以及 VS2010(除了我提到的例外)。跨度>
  • @towi 看起来它还没有在 gcc 中实现。只有 MSVC 和 libc++。
【解决方案2】:

我编写了辅助函数来与 UTF8 字符串 (C++11) 进行转换:

#include <string>
#include <locale>
#include <codecvt>

using namespace std;

template <typename T>
string toUTF8(const basic_string<T, char_traits<T>, allocator<T>>& source)
{
    string result;

    wstring_convert<codecvt_utf8_utf16<T>, T> convertor;
    result = convertor.to_bytes(source);

    return result;
}

template <typename T>
void fromUTF8(const string& source, basic_string<T, char_traits<T>, allocator<T>>& result)
{
    wstring_convert<codecvt_utf8_utf16<T>, T> convertor;
    result = convertor.from_bytes(source);
}

使用示例:

// Unicode <-> UTF8
{
    wstring uStr = L"Unicode string";
    string str = toUTF8(uStr);

    wstring after;
    fromUTF8(str, after);
    assert(uStr == after);
}

// UTF16 <-> UTF8
{
    u16string uStr;
    uStr.push_back('A');
    string str = toUTF8(uStr);

    u16string after;
    fromUTF8(str, after);
    assert(uStr == after);
}

【讨论】:

    【解决方案3】:

    据我所知,C++ 没有提供从 UTF-32 转换为 UTF-32 或转换为 UTF-32 的标准方法。但是,对于 UTF-16,有方法 mbstowcs(多字节到宽字符串)和相反的方法,wcstombs

    如果您也需要 UTF-32,则需要 iconv,它在 POSIX 2001 中但不在标准 C 中,因此在 Windows 上您需要像 libiconv这样的替代品>.

    这是一个关于如何使用 mbstowcs 的示例:

    #include <string>
    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    wstring widestring(const string &text);
    
    int main()
    {
      string text;
      cout << "Enter something: ";
      cin >> text;
    
      wcout << L"You entered " << widestring(text) << ".\n";
      return 0;
    }
    
    wstring widestring(const string &text)
    {
      wstring result;
      result.resize(text.length());
      mbstowcs(&result[0], &text[0], text.length());
      return result;
    }
    

    反过来是这样的:

    string mbstring(const wstring &text)
    {
      string result;
      result.resize(text.length());
      wcstombs(&result[0], &text[0], text.length());
      return result;
    }
    

    Nitpick:是的,我知道,wchar_t 的大小是实现定义的,所以它可以是 4 字节 (UTF-32)。但是,我不知道有哪个编译器可以做到这一点。

    【讨论】:

    • Linux 上的 GCC 使用 UTF-32 表示 wchar_t
    • 据我所知,Windows 是唯一一个将 UTF-16 用于 wstring 的通用平台。
    • 可能不算“普通”,但我认为 AIX 使用 2 字节 wchar_t 和 UTF-16。
    • 反向函数的问题是您可能需要一个缓冲区,其中的元素比原始字符串中的字符多,例如如果您用日语转换了一个宽字符串,并且它被转换为 S-JIS,那么文本将被截断。如果您使用 NULL 作为第一个参数调用 wcstombs,则该函数将返回存储原始字符串中所有字符所需的缓冲区大小。此外,在 C++11 之前,不能保证 std::string 中的元素是连续存储的,而从 C++11 开始,有 std::codecvt 这使得整个考验变得微不足道。
    猜你喜欢
    • 2013-04-20
    • 2014-09-17
    • 1970-01-01
    • 2021-09-13
    • 2010-11-29
    • 1970-01-01
    • 1970-01-01
    • 2017-09-06
    相关资源
    最近更新 更多