我现在刚刚为你实现了一个函数DecodeUtf16Char(),它基本上可以做两件事 - 要么检查它是否是有效的 utf-16(当 check_only = true 时),要么检查并返回有效的解码 Unicode代码点(32 位)。它还支持两字节 utf-16 字中的大端(默认,big_endian = true)或小端(big_endian = false)字节顺序。 bad_skip 等于如果无法解码字符(无效 utf-16)则要跳过的字节数,bad_value 是一个值,用于表示默认情况下未解码 utf-16(无效)是-1。
此函数定义后包含使用/测试示例。基本上你只需将开始(ptr)和结束指针传递给这个函数,当返回检查返回值时,如果它是-1,那么指针begin是无效的utf-16序列,如果不是-1那么此返回值包含有效的 32 位 unicode 代码点。我的函数也会增加ptr,如果是有效的utf-16,则增加解码字节数;如果无效,则增加bad_skip字节数。
我的函数应该非常快,因为它只包含几个 ifs(加上一些算术,以防您要求实际解码字符),总是将我的函数放入标题中,以便它内联到调用函数中以产生非常快速代码!同样只传入编译时常量check_only 和big_endian,这将通过C++ 优化去除额外的解码代码。
例如,如果您只想检测 utf-16 字节的长时间运行,那么您接下来要做的是,在调用此函数的循环中迭代,只要它第一次返回不是 -1,那么它就有可能开始,然后进一步迭代并捕获最后一个不等于-1 值,这将是文本的最后一点。同样重要在搜索 utf-16 字节时传入 bad_skip = 1,因为有效的 char 可以从任何字节开始。
我用于测试不同的字符 - 英文 ASCII、俄文字符(两字节 utf-16)加上两个 4 字节字符(两个 utf-16 字)。我的测试将转换后的行附加到test.txt 文件中,该文件采用 UTF-8 编码以便于查看,例如通过notepad。我的解码功能之后的所有代码都不需要它的工作,剩下的只是测试代码。
我的功能需要两个功能 - _DecodeUtf16Char_ReadWord()(助手)加上DecodeUtf16Char()(主解码器)。我只包含一个标准头 <cstdint>,如果不允许包含任何内容,则只需定义 uint8_t 和 uint16_t 和 uint32_t,我只使用此头中的这些类型定义。
另外,作为参考,see my other post 从头开始(并使用标准 C++ 库)实现了 UTF-8UTF-16UTF-32!
之间的所有类型的转换
Try it online!
#include <cstdint>
static inline bool _DecodeUtf16Char_ReadWord(
uint8_t const * & ptrc, uint8_t const * end,
uint16_t & r, bool const big_endian
) {
if (ptrc + 1 >= end) {
// No data left.
if (ptrc < end)
++ptrc;
return false;
}
if (big_endian) {
r = uint16_t(*ptrc) << 8; ++ptrc;
r |= uint16_t(*ptrc) ; ++ptrc;
} else {
r = uint16_t(*ptrc) ; ++ptrc;
r |= uint16_t(*ptrc) << 8; ++ptrc;
}
return true;
}
static inline uint32_t DecodeUtf16Char(
uint8_t const * & ptr, uint8_t const * end,
bool const check_only = true, bool const big_endian = true,
uint32_t const bad_skip = 1, uint32_t const bad_value = -1
) {
auto ptrs = ptr, ptrc = ptr;
uint32_t c = 0;
uint16_t v = 0;
if (!_DecodeUtf16Char_ReadWord(ptrc, end, v, big_endian)) {
// No data left.
c = bad_value;
} else if (v < 0xD800 || v > 0xDFFF) {
// Correct single-word symbol.
if (!check_only)
c = v;
} else if (v >= 0xDC00) {
// Unallowed UTF-16 sequence!
c = bad_value;
} else { // Possibly double-word sequence.
if (!check_only)
c = (v & 0x3FF) << 10;
if (!_DecodeUtf16Char_ReadWord(ptrc, end, v, big_endian)) {
// No data left.
c = bad_value;
} else if ((v < 0xDC00) || (v > 0xDFFF)) {
// Unallowed UTF-16 sequence!
c = bad_value;
} else {
// Correct double-word symbol
if (!check_only) {
c |= v & 0x3FF;
c += 0x10000;
}
}
}
if (c == bad_value)
ptr = ptrs + bad_skip; // Skip bytes.
else
ptr = ptrc; // Skip all eaten bytes.
return c;
}
// --------- Next code only for testing only and is not needed for decoding ------------
#include <iostream>
#include <string>
#include <codecvt>
#include <fstream>
#include <locale>
static std::u32string DecodeUtf16Bytes(uint8_t const * ptr, uint8_t const * end) {
std::u32string res;
while (true) {
if (ptr >= end)
break;
uint32_t c = DecodeUtf16Char(ptr, end, false, false, 2);
if (c != -1)
res.append(1, c);
}
return res;
}
#if (!_DLL) && (_MSC_VER >= 1900 /* VS 2015*/) && (_MSC_VER <= 1914 /* VS 2017 */)
std::locale::id std::codecvt<char16_t, char, _Mbstatet>::id;
std::locale::id std::codecvt<char32_t, char, _Mbstatet>::id;
#endif
template <typename CharT = char>
static std::basic_string<CharT> U32ToU8(std::u32string const & s) {
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf_8_32_conv;
auto res = utf_8_32_conv.to_bytes(s.c_str(), s.c_str() + s.length());
return res;
}
template <typename WCharT = wchar_t>
static std::basic_string<WCharT> U32ToU16(std::u32string const & s) {
std::wstring_convert<std::codecvt_utf16<char32_t, 0x10ffffUL, std::little_endian>, char32_t> utf_16_32_conv;
auto res = utf_16_32_conv.to_bytes(s.c_str(), s.c_str() + s.length());
return std::basic_string<WCharT>((WCharT*)(res.c_str()), (WCharT*)(res.c_str() + res.length()));
}
template <typename StrT>
void OutputString(StrT const & s) {
std::ofstream f("test.txt", std::ios::binary | std::ios::app);
f.write((char*)s.c_str(), size_t((uint8_t*)(s.c_str() + s.length()) - (uint8_t*)s.c_str()));
f.write("\n\x00", sizeof(s.c_str()[0]));
}
int main() {
std::u16string a = u"привет|мир|hello|?|world|?|again|русский|english";
*((uint8_t*)(a.data() + 12) + 1) = 0xDD; // Introduce bad utf-16 byte.
// Also truncate by 1 byte ("... - 1" in next line).
OutputString(U32ToU8(DecodeUtf16Bytes((uint8_t*)a.c_str(), (uint8_t*)(a.c_str() + a.length()) - 1)));
return 0;
}
输出:
привет|мир|hllo|?|world|?|again|русский|englis