【发布时间】:2011-11-06 03:47:09
【问题描述】:
我一直在寻找一种在 Unicode 字符串类型之间进行转换的方法,结果遇到了this method。不仅我没有完全理解方法(没有cmet)而且文章暗示将来会有更好的方法。
如果这是最好的方法,请您指出是什么使它起作用,如果不是,我想听听有关更好方法的建议。
【问题讨论】:
标签: c++ string unicode c++11 unicode-string
我一直在寻找一种在 Unicode 字符串类型之间进行转换的方法,结果遇到了this method。不仅我没有完全理解方法(没有cmet)而且文章暗示将来会有更好的方法。
如果这是最好的方法,请您指出是什么使它起作用,如果不是,我想听听有关更好方法的建议。
【问题讨论】:
标签: c++ string unicode c++11 unicode-string
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 被定义为可以将任何语言环境的 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 并没有,即使 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 显然对于简化文本处理或作为与区域设置无关的文本的存储没有用。可移植代码不应尝试将其用于这些目的。
【讨论】:
我编写了辅助函数来与 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-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)。但是,我不知道有哪个编译器可以做到这一点。
【讨论】:
wchar_t。
wcstombs,则该函数将返回存储原始字符串中所有字符所需的缓冲区大小。此外,在 C++11 之前,不能保证 std::string 中的元素是连续存储的,而从 C++11 开始,有 std::codecvt 这使得整个考验变得微不足道。