我的问题的一部分来自我的误解,或者不完全理解string和wstring类在 C++ 中是如何工作的(我来自 C# 背景)。这个很好的答案已经描述了两者的区别和利弊:std::wstring VS std::string。
string 和 wstring 的工作原理
对我来说,关于 string 和 wstring 类的最重要的发现是它们在语义上并不代表一段编码文本,而只是一个 char 或 wchar_t 的“字符串”。它们更像是一个简单的数据数组,带有一些特定于字符串的操作(如 append 和 substr),而不是表示文本。他们都不知道任何类型的字符串编码,他们将每个 char 或 wchar_t 元素单独处理为单独的字符。
编码
但是,在大多数系统上,如果您从带有特殊字符的字符串文字创建字符串,如下所示:
std::string s("ű");
ű字符将由内存中的一个以上字节表示,但这与 std::string 类无关,这是编译器的一个特性,因为它可以使用 UTF8 编码字符串文字(虽然不是每个编译器)。(并且以 L 为前缀的字符串文字将由 wchar_t-s 以 UTF16 或 UTF32 或其他方式表示,具体取决于编译器)。
因此字符串“ű”将在内存中用两个字节表示:0xC5 0xB1,并且 std::string 类不会知道这两个字节在语义上意味着 UTF8 中的一个字符(一个 Unicode 代码点),因此示例代码:
std::string s("ű");
std::cout << s.length() << std::endl;
std::cout << s.substr(0, 1);
产生以下结果(取决于编译器,一些编译器不将字符串文字视为 UTF8,而一些编译器依赖于源文件的编码):
2
�
size() 函数返回 2,因为 std::string 唯一知道的是它存储了两个字节(两个字符)。并且 substr 也“原始地”工作,它返回一个包含单个字符0xC5的字符串,它显示为 �,因为它不是有效的 UTF8 字符(但这不会打扰 std::string)。
从中我们可以看出,处理编码的是平台的各种文本处理 API,比如简单的cout或DirectWrite。
我的方法
在我的应用程序中,DirectWrite 非常重要,它只接受以 UTF16 编码的字符串(以 wchar_t* 指针的形式)。所以我决定将字符串存储在内存和以 UTF16 编码的文件中。但是,我想要一个跨平台的实现,它可以在 Windows、Android 和 iOS 上处理 UTF16 字符串,而std::wstring无法做到这一点,因为它的数据大小(以及它适合使用的编码)是平台相关的。
为了创建一个跨平台、严格的 UTF16 字符串类,我将basic_string模板化为 2 字节长的数据类型。非常令人惊讶 - 至少对我来说 - 我几乎没有在网上找到关于此的信息,我的解决方案基于这种方法。这是代码:
// Define this on every platform to be 16 bytes!
typedef unsigned short char16;
struct char16_traits
{
typedef char16 _E;
typedef _E char_type;
typedef int int_type;
typedef std::streampos pos_type;
typedef std::streamoff off_type;
typedef std::mbstate_t state_type;
static void assign(_E& _X, const _E& _Y)
{_X = _Y; }
static bool eq(const _E& _X, const _E& _Y)
{return (_X == _Y); }
static bool lt(const _E& _X, const _E& _Y)
{return (_X < _Y); }
static int compare(const _E *_U, const _E *_V, size_t _N)
{return (memcmp(_U, _V, _N * 2)); }
static size_t length(const _E *_U)
{
size_t count = 0;
while(_U[count] != 0)
{
count++;
}
return count;
}
static _E * copy(_E *_U, const _E *_V, size_t _N)
{return ((_E *)memcpy(_U, _V, _N * 2)); }
static const _E * find(const _E *_U, size_t _N, const _E& _C)
{
for(int i = 0; i < _N; ++i) {
if(_U[i] == _C) {
return &_U[i];
}
}
return 0;
}
static _E * move(_E *_U, const _E *_V, size_t _N)
{return ((_E *)memmove(_U, _V, _N * 2)); }
static _E * assign(_E *_U, size_t _N, const _E& _C)
{
for(size_t i = 0; i < _N; ++i) {
assign(_U[i], _C);
}
return _U;
}
static _E to_char_type(const int_type& _C)
{return ((_E)_C); }
static int_type to_int_type(const _E& _C)
{return ((int_type)(_C)); }
static bool eq_int_type(const int_type& _X, const int_type& _Y)
{return (_X == _Y); }
static int_type eof()
{return (EOF); }
static int_type not_eof(const int_type& _C)
{return (_C != eof() ? _C : !eof()); }
};
typedef std::basic_string<unsigned short, char16_traits> utf16string;
字符串用上面的类存储,原始的 UTF16 数据被传递给各个平台的特定 API 函数,目前所有这些似乎都支持 UTF16 编码。
实现可能并不完美,但是 append、substr 和 size 函数似乎可以正常工作。我在 C++ 中的字符串处理方面仍然没有太多经验,所以如果我陈述不正确,请随时评论/编辑。