46

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

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

4

3 回答 3

93

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 方面。以下是一些易于与 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 在所有语言环境中使用相同的 wchar_t 语言环境,Windows 也不会。

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 显然对于简化文本处理或作为与区域设置无关的文本的存储没有用。可移植代码不应尝试将其用于这些目的。

于 2011-08-29T19:58:53.710 回答
15

我已经编写了辅助函数来转换为 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);
}
于 2015-07-13T16:49:34.413 回答
-2

据我所知,C++ 没有提供从 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)。但是,我不知道执行此操作的编译器。

于 2011-08-29T16:31:13.720 回答