假设我有一个std::string
,但数据以 UTF-16 编码。
我怎么能将该数据复制到 中std::wstring
,而不是修改数据?
此外,我不能只使用std::wstring
,因为我正在在线检索文本文件并检查Content-Type
标题字段以确定编码。但std::string
用于接收数据。
std::wstring PackUTF16(const std::string & input)
{
if (input.size() % 2 != 0)
throw std::invalid_argument("input length must be even");
std::wstring result(input.size() / 2, 0);
for (int i = 0; i < result.size(); ++i)
{
result[i] = (input[2*i+1] & 0xff) << 8 | (input[2*i] & 0xff); // for little endian
//result[i] = (input[2*i] & 0xff) << 8 | (input[2*i+1] & 0xff); // for big endian
}
return result;
}
开始时有一个 BOM(字节顺序标记),然后您检查它以确定字节顺序。否则,最好知道字节顺序,即最低有效字节或最高有效字节排在第一位。如果您不知道字节顺序并且没有 BOM,那么您只需要尝试一种或两种并应用一些统计测试和/或涉及人工决策者 (HDM)。
假设这个 Little Endian 字节顺序,即最低有效字节在前。
然后对于每对字节,例如
w.push_back( (UnsignedChar( s[2*i + 1] ) << 8u) | UnsignedChar( s[2*i] ) );
其中w
a std::wstring
,i
是宽字符 < 的索引s.length()/2
,UnsignedChar
是 a typedef
,是保存数据的 a unsigned char
, 8 是每个字节的位数, 即您必须假设或静态断言来自标头的是 8。s
std::string
CHAR_BITS
<limits.h>
试试这个:
static inline std::wstring charToWide(const std::string & s_in)
{
const char * cs = s_in.c_str();
size_t aSize;
if( ::mbsrtowcs_s(&aSize, NULL, 0, &cs, 0, NULL) != 0)
{
throw std::exception("Cannot convert string");
}
std::vector<wchar_t> aBuffer(aSize);
size_t aSizeSec;
if (::mbstowcs_s(&aSizeSec, &aBuffer[0], aSize, cs, aSize) != 0)
{
throw std::exception("Cannot convert string");
}
return std::wstring(&aBuffer[0], aSize - 1);
}
因此,您已将代表 UTF-16 编码字符串的一系列字节插入std::string
. 大概您正在执行诸如反序列化表示 UTF-16 的字节之类的操作,并且用于检索要反序列化的字节的 API 指定了 std::string。我认为这不是最好的设计,但是您将处理将其转换为 wstring 的处理方式与处理将字节转换为浮点数或其他任何内容的处理方式相同;验证字节缓冲区,然后将其转换:
char c[] = "\0a\0b\xd8\x3d\xdc\x7f";
std::string buf(std::begin(c),std::end(c));
assert(0==buf.size()%2);
std::wstring utf16(reinterpret_cast<wchar_t const *>(buf.data()),buf.size()/sizeof(wchar_t));
// also validate that each code unit is legal, and that there are no isolated surrogates
要记住的事情:
* UTF-16 实际上并不满足 C++ 语言对 wchar_t 编码的要求,但有些平台无论如何都会使用它。这会导致一些标准 API 出现问题,这些 API 应该处理代码点,但不能仅仅因为代表 UTF-16 代码单元的 wchar_t 不能代表所有平台的代码点。
这是一个不依赖于平台特定细节的实现,只需要 wchar_t 足够大以容纳 UTF-16 代码单元,并且每个 char 恰好容纳 UTF-16 代码单元的 8 位。但它实际上并没有验证 UTF-16 数据。
#include <string>
#include <cassert>
#include <iterator>
#include <algorithm>
#include <iostream>
enum class endian {
big,little,unknown
};
std::wstring deserialize_utf16be(std::string const &s) {
assert(0==s.size()%2);
std::wstring ws;
for(size_t i=0;i<s.size();++i)
if(i%2)
ws.back() = ws.back() | ((unsigned char)s[i] & 0xFF);
else
ws.push_back(((unsigned char)s[i] & 0xFF) << 8);
return ws;
}
std::wstring deserialize_utf16le(std::string const &s) {
assert(0==s.size()%2);
std::wstring ws;
for(size_t i=0;i<s.size();++i)
if(i%2)
ws.back() = ws.back() | (((unsigned char)s[i] & 0xFF) << 8);
else
ws.push_back((unsigned char)s[i] & 0xFF);
return ws;
}
std::wstring deserialize_utf16(std::string s, endian e=endian::unknown) {
static_assert(std::numeric_limits<wchar_t>::max() >= 0xFFFF,"wchar_t must be large enough to hold UTF-16 code units");
static_assert(CHAR_BIT>=8,"char must hold 8 bits of UTF-16 code units");
assert(0==s.size()%2);
if(endian::big == e)
return deserialize_utf16be(s);
if(endian::little == e)
return deserialize_utf16le(s);
if(2<=s.size() && ((unsigned char)s[0])==0xFF && ((unsigned char)s[1])==0xFE)
return deserialize_utf16le(s.substr(2));
if(2<=s.size() && ((unsigned char)s[0])==0xfe && ((unsigned char)s[1])==0xff)
return deserialize_utf16be(s.substr(2));
return deserialize_utf16be(s);
}
int main() {
char c[] = "\xFF\xFE\x61\0b\0\x3d\xd8\x7f\xdc";
std::string buf(std::begin(c),std::end(c)-1);
std::wstring utf16 = deserialize_utf16(buf);
std::cout << std::hex;
std::copy(begin(utf16),end(utf16),std::ostream_iterator<int>(std::cout," "));
std::cout << "\n";
}