这个例程是在书上看到的,感觉很有意思。
简单介绍一下utf-8编码,utf-8编码是一种变长的编码方式,长度为1-4字节。
当码长为1字节的时候,兼容ascii编码,格式为0xxxxxxx (x处表示有效位)
当码长为2字节的时候,格式为110xxxxx 10xxxxxx
高字节的110表示码长为2字节,低字节的10为低字节标志位,下同
当码长为3字节的时候,格式为1110xxxx 10xxxxxx 10xxxxxx
当码长为4字节的时候,格式为11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
我们先来看一下头文件 unicodeUtf8.h
/*这段代码是utf8编码解码器头文件,在书p66*/
#ifndef _INCLUDE_UTF8_H
#define _INCLUDE_UTF8_H
#include <stdint.h>
uint32_t getByteNumOfEncodeUtf8(int value);
uint32_t getByteNumOfDecodeUtf8(uint8_t byte);
uint8_t encodeUtf8(uint8_t* buf, int value);
int decodeUtf8(const uint8_t* bytePtr, uint32_t length);
#endif
getByteNumOfEncodeUtf8 计算编码后的字节长度
getByteNumOfDecodeUtf8 计算解码后的字节长度
encodeUtf8 编码
decodeUtf8 解码
具体的实现代码
/*这段代码是utf8编码解码器具体实现代码,在书p61*/
#include "unicodeUtf8.h"
#include "common.h"
/*详细的ascii码编码规则见书P60*/
//返回value按照utf8编码后的字节数
uint32_t getByteNumOfEncodeUtf8(int value){
ASSERT(value>0, "can't encode negative value!\n"); //只能对正数编码
//单个ASCII码字符需要1个字节
if(value <= 0x7f){
return 1;
}
//此范围需要2字节
if(value <= 0x7ff){
return 2;
}
//此范围需要3字节
if(value <= 0xffff){
return 3;
}
//此范围需要4字节
if(value <= 0x10ffff){
return 4;
}
//超过范围返回0
return 0;
}
//把value编码为utf8后写入缓冲区buf,返回写入的字节数
//详细的解释在书p65
uint8_t encodeUtf8(uint8_t* buf, int value){
ASSERT(value>0, "can't encode negative value!\n"); //只能对正数编码
//按照大端字节序写入缓冲区(低地址对应高数据位)
if(value <= 0x7f){
*buf = value & 0x7f; //讲value存入内存中
return 1;
}
else if(value <= 0x7ff){
//这种情况下utf8格式为110xxxxx 10xxxxxx
//先在低地址处写入高字节110xxxxx
//11000000 | ((value & 0111 1100 0000)>>6)
*buf++ = 0xc0 | ((value & 0x7c0)>>6);
//再在高地址处写入低字节10xxxxxx
//1000 0000 | (value & 0011 1111)
*buf = 0x80 | (value & 0x3f);
return 2;
}
else if(value <= 0xffff){
//这种情况下utf8格式为1110xxxx 10xxxxxx 10xxxxxx
//先在低地址写入高字节1110xxxx
*buf++ = 0xe0 | ((value & 0xf0000)>>12);
//然后在中地址写入中字节
*buf++ = 0x80 | ((value & 0xfc0)>>6);
//最后在高地址写入低字节
*buf = 0x80 | (value & 0x3f);
return 3;
}
else if(value <= 0x10ffff){
//在这种情况下utf8格式为11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
//先在低地址写入高字节11110xxx
*buf++ = 0xf0 | ((value & 0x1c0000)>>18);
//再在第二段地址写入第二段字节10xxxxxx
*buf++ = 0x80 | ((value & 0x3f00)>>12);
//然后在第三段地址写入第三段字节10xxxxxx
*buf++ = 0x80 | ((value & 0xfc0)>>6);
//最后在高地址写入低字节10xxxxxx
*buf = 0x80 | (value & 0x3f);
return 4;
}
NOT_REACHED(); //如果程序运行到这里就是错了
return 0;
}
//返回解码utf8的字节数
uint32_t getByteNumOfDecodeUtf8(uint8_t byte){
//byte应该是utf8的最高1字节,如果指向了utf8编码后面低字节部分则返回0
if((byte & 0xc0)==0x80) return 0; //1000 0000
if((byte & 0xf8)==0xf0) return 4; //1111 0000
if((byte & 0xf0)==0xf0) return 3; //1110 0000
if((byte & 0xe0)==0xc0) return 2; //1100 0000
return 1; //ASCII码
}
//解码以bytePtr为起始地址的utf8序列,其最大长度为length,若不是utf8序列就返回-1
int decodeUtf8(const uint8_t* bytePtr, uint32_t length){
//若是1字节的ascii码: 0xxxxxxx
if(*bytePtr <= 0x7f) return *bytePtr;
int value;
uint32_t remainingBytes;
//先读取高1字节
//根据高1字节的高n位判断相应字节数的utf8编码
if((*bytePtr & 0xe0)==0xc0){
//若是2字节的utf8
value = *bytePtr & 0x1f; //记录后面的5位有效位
remainingBytes = 1;
}
else if((*bytePtr & 0xf0)==0xe0){
//若是3字节的utf8
value = *bytePtr & 0x0f; //记录后面的4位有效位
remainingBytes = 2;
}
else if((*bytePtr & 0xf8)==0xf0){
//若是4字节的utf8
value = *bytePtr & 0x07;
remainingBytes = 3;
}
else {return -1;} //非法编码
//如果utf8被斩断了就不再读下去了
if(remainingBytes > length - 1){return -1;}
//再读取低字节中的数据
while(remainingBytes > 0){
bytePtr++;
remainingBytes--;
//高两位必须是10
if((*bytePtr & 0xc0) != 0x80){return -1;}
//从次高字节往低字节读
value = value << 6 | (*bytePtr & 0x3f); //value左移6为写入6位有效位
}
return value; //返回解码得到的value值
}
下面我就简单介绍一下编码和解码的算法
编码
我们以3字节码长为例, 当我们的value是12-16位二进制数的时候就应该编码成3字节
我们采用大端字节序,也就是数据位的高位存放在内存的低地址处,数据位的地位存放在内存的高地址处
所以我们先存高1字节,高1字节的格式为 1110 xxxx ,有4位有效为,这4位有效位是value的高4位
我们把value当16位二进制数来看
首先用 value & 0xf000 (也就是 1111 0000 0000 0000) 然后右移12位来得到高4位
之后用0xe0(即1110 0000)或 来得到头标志1110
编码第二个字节的时候也是同样的道理 我们要将value 的第7-12为写入编码中
首先 value & 0xfc0 (即1111 1100 0000)来获得7-12位 然后右移6位
之后用0x80 (即1000 0000)来得到次字节表示10
最后使用第6位写入编码
先用value & 0x3f ( 即0011 1111 )来获得低6位
然后使用0x80获得头10
解码
解码的时候我们先获取高1字节来判断后面有几个字节,然后再继续解码
如果只有一个字节那么它就是ascii码,直接返回即可
如果是两个字节,也就是说 (*bytePtr & 0xe0) == 0xc0 的时候我们还要继续往后读一个字节,此时最高位上有5位有效位
value = *bytePtr & 0x1f 得到5位有效位
三字节和四字节同理,并且有效位逐个减小一位
之说以用0xe0与是因为头标志为110,之后才是有效的数据位
在我们读后面的字节的时候用 (*bytePtr & 0xc0) == 0x80 来判断它是正确编码
然后读取第6位最为有效位写入value中
先 *bytePtr & 0x3f 来获取低6位, 然后value 左移6位再或上得到的低6为即可
参考书籍 《基于c语言自制编程语言》作者 郑钢