问题描述
在处理多字节字符串时,我经常需要做的一件事就是删除它的最后一个字符。我如何找到最后一个字符,以便我可以使用正常的字节操作将其切掉,最好是尽可能少地读取?
请注意,此问题适用于大多数,如果不是所有,多字节编码。像 UTF-8 这样的自同步编码的答案是微不足道的,因为您可以在字节串中从右到左作为开始标记。
解决方法
答案将使用 C 语言编写,带有 POSIX 多字节函数。所述功能也可在 Windows 上找到。假设字节串以 len
结尾,并且到目前为止格式良好;假设适当的 setlocale
调用。移植到 mbrlen
留给读者作为练习。
天真的解决方案
显然正确的解决方案涉及“按预期”解析编码,从左到右。
ssize_t index_of_last_char_left(const char *c,size_t len) {
size_t pos = 0;
size_t next = 1;
mblen(NULL,0);
while (pos < len - 1) {
next = mblen(c + pos,len - pos);
if (next == -1) // Invalid input
return pos;
pos += next;
}
return pos - next;
}
像这样删除多个字符会导致“不小心二次方”的情况;记住中间位置会有所帮助,但需要额外的管理。
从右到左的解决方案
正如我在问题中提到的,对于自同步编码,唯一要做的就是寻找开始标记。但是那些不进行自我同步的人有什么问题呢?
- 一或二字节 EUC encodings 的两字节序列的两个字节都高于
0x7f
,并且开始和继续字节之间几乎没有区别。为此,我们可以检查mblen(pos) == bytes_left
,因为我们知道字符串格式正确。 - Big5、GBK 和 GB10830 编码还允许 ASCII 范围内的连续字节,因此后视是强制性的。
清除后(并假设直到 len
的字节串格式正确),我们可以:
// As much as CJK encodings do. I don't have time to see if it works for UTF-1.
#define MAX_MB_LEN 4
ssize_t index_of_last_char_right(const char *c,size_t len) {
ssize_t pos = len - 1;
bool last = true;
bool last_is_okay = false;
assert(!mblen(NULL,0)); // No,we really cannot handle shift states.
for (; pos >= 0 && pos >= len - 2 - MAX_MB_LEN; pos--) {
int next = mblen(c + pos,len - pos);
bool okay = (next > 0) && (next == len - pos - 1);
if (last) {
last_is_okay = okay;
last = false;
} else if (okay)
return pos;
}
return last_is_okay ? len - 1 : -1;
}
(您应该能够通过 (next > 0) && (next <= len - pos - 1)
找到格式错误的字符串的最后一个好的字符。但是当最后一个字节没问题时不要返回它!)
这有什么意义?
上面的代码示例适用于不想只编写“UTF-8 支持”而是基于 C 库的“语言环境支持”的理想主义者。 2021 年可能根本没有这个意义:)