我的问题的一部分来自于我对C++中
string和
wstring类如何工作的误解或不完全理解(我来自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字符串类,我在一个
长度为2个字节的数据类型上对basic_string进行了模板化。令人惊讶的是,至少对我来说,在网上几乎没有关于这个的信息,我基于
这个方法来实现。以下是代码:
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++中的字符串处理还没有很多经验,如果我说错了什么,请随时评论/编辑。