UTF-8的编码规则如下:
U+ 0000 ~ U+ 007F: 0XXXXXXX
U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX
U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX
U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
GBK字符的UTF-8编码是三个字节。
其十六进制示例如下 :e8 82 a1,对应的UNICODE为: a1 80,对应的GBK编码为 b9 c9
现在将UTF-8编码用二进制表示为:1110,1000 1000,0010 1010,0001,去掉每个字节的字头后为两个字节,表示如下:
1000,0000 1010,0001 表示成十六进制为 0x80 0xa1,与高头的UNICODE编码是相同的,只是字节顺序不一样。
通过查表就可以得到 0xc9b9 = cp936[0x80a1]
编码表 cp936[] 可以从 libiconv的源代码中找到。
整个函数如下:
int utf8_to_gbk(unsigned char *utf8buf, int bufsiz)
{
unsigned char *pos, *iconvpos, *ptop;
iconvpos = pos = utf8buf;
ptop = pos + bufsiz;
while (pos < ptop) {
unsigned char ch = *pos;
if (0xe0 == (ch & 0xf0)) {
WORD u16val = ((ch & 0xf) << 12) | ((pos[1] & 0x3f) << 6) | (pos[2] & 0x3f);
*(WORD UNALIGNED *)iconvpos = _byteswap_ushort(cp936[u16val]);
iconvpos += 2;
pos += 3;
}
else if (iconvpos != pos++) {
*iconvpos++ = ch;
}
}
return (iconvpos - utf8buf);
}
自己实现的好处是省略了分配新的数组的麻烦。因为UTF-8编码比GBK编码要长,直接在同一个数组内转换就可以了。