我一直在寻找一种在 Unicode 字符串类型之间进行转换的方法,并遇到了这种方法。不仅我不完全理解方法(没有评论),而且文章暗示将来会有更好的方法。
如果这是最好的方法,请您指出是什么使它起作用,如果不是,我想听听有关更好方法的建议。
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 被定义为可以将任何语言环境的 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 作为语言环境之间文本的通用表示,也不能使用简单的文本算法。
不多,反正对于可移植的代码。如果__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 显然对于简化文本处理或作为与区域设置无关的文本的存储没有用。可移植代码不应尝试将其用于这些目的。
我已经编写了辅助函数来转换为 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);
}
据我所知,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)。但是,我不知道执行此操作的编译器。