c++转码基础(1):各种编码类型及unicode和uft-8互转

来源:互联网 发布:南京和知梦设计研究院 编辑:程序博客网 时间:2024/05/22 06:58

什么是Ascii编码?

单字节编码,适用于所有拉丁文字字母.

ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符.

标准ASCII 码也叫基础ASCII码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号, 以及在美式英语中使用的特殊控制字符。其中:

0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;

     通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8、9、10 和13 分别转换为退格、制表、换行和回车字符。

     它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响

32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。

扩展ASCII 字符是从128 到255(0x80-0xff)的字符.扩展ASCII不再是国际标准

 

什么是DBCS字符集?unicode的由来

 中文常用的字符集有GB2312-80,GBK(大陆的中文字符集),Big5(台湾的倚天汉字系统),unicode.

为了解决中国、日本和韩国的象形文字符和ASCII的某种兼容性,开发出双字节字符集(DBCS:double-byte character set)。

DBCS从256代码开始,就像ASCII一样。较高的128个代码中的某些总是跟随着第二个字节。这两个字节一起(称作首字节和跟随字节)定义一个字符,通常是一个复杂的象形文字。

为了解决中文字符的应用,中国定义了一套GB2312的字符集:

  字符集规定:127号之后的奇异符号们直接取消掉, 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,

  前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。

  在这些编码里,我们还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,

  这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。 中国人民看到这样很不错,于是就把这种汉字方案叫做 “GB2312“。GB2312 是对 ASCII 的中文扩展。

  但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。

  后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字 符集里的内容。

  结果扩展之后的编码方案被称为 GBK 标准,GBK包括了GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK扩成了 GB18030。

  从此之后,中华民族的文化就可以在计算机时代中传承了。 中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 “DBCS“(Double Byte Charecter Set 双字节字符集)

后来因为很多地区都定义自己的编码字符集合,而且都是重叠的,所以在跨区域通讯向出现了问题. ISO (国际标谁化组织)为了解决这个问题,废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号 的编码!他们打算叫它”Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “unicode“。

 

unicode编码的形式?UTF-8的由来?

 unicode开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,

 对于ASCII里的那些“半角”字符,unicode包持其原编码不变,只是将其长度由原 来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。

 由于”半角”英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在 保存英文文本时会多浪费一倍的空间。

如以下字符编码的unicode 编码(UCS-2):

字符     十六进制编码         二进制编码

I          0049               00000000 01001001

t          0074               00000000 01110100

'          0027               00000000 00100111

s          0073               00000000 01110011

           0020               00000000 00100000        

        77e5               01110111 11100101

        4e4e               01001110 01001110

        65e5               01100101 11100101

        62a5               01100010 10100101

 

unicode很长一段时间无法推广的原因有两个:

 1. unicode 是双字符编码,在网络传输和不同机器上存在大小端的问题;相同的字符集在不同的位置存在几码顺序不同。

 2. unicode 1~127的文字符浪费一个字节的空间

为了解决unicode在网络上传输的问题,出现了面向传输的UTF(UCS Transfer Format)标准,UTF-8就是每次8位传输数据,UTF-16就是每次16位数据(先有的UTF-16,但是和unicode一样的问题)。

UTF-8就是在互联网上使用最广的一种unicode的实现方式,这是为传输而设计的编码,并使编码无国界。

 

 

 

UTF-8编码格式?

UTF-8最大的一个特点,就是它是一种变长的编码方式.

它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度,当字符在ASCII 码的范围时,就用一个字节表示,保留了ASCII字符一个字节的编码做为它的一部分,

 注意的是unicode一个中文字符占2个字节,而UTF-8一个中 文字符占3个字节)。从unicode到uft-8并不是直接的对应,而是要过一些算法和规则来转换。

UTF-8是这样做的:

1. 单字节的字符,字节的第一位设为0,对于英语文本,UTF-8码只占用一个字节,和ASCII码完全相同;

2. n个字节的字符(n>1),第一个字节的前n位设为1,第n+1位设为0,后面字节的前两位都设为10,这n个字节的其余空位填充该字符unicode码,高位用0补足。

这样就形成了如下的UTF-8标记位:

(十六进制)          | (二进制)

—————————————————————–

0000 0000-0000 007F | 0xxxxxxx

0000 0080-0000 07FF | 110xxxxx 10xxxxxx

0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

 

如以下字符编码UTF-8:

字符     十六进制编码         二进制编码

I          49                 01001001

t          74                 01110100

'          27                 00100111

s          73                 01110011

           20                 00100000        

        E79FA5             11100111 10011111 10100101

        E4B98E             11100100 10111001 10001110

        E697A5             11100110 10010111 10100101

        E68AA5             11100110 10001010 10100101

 

UTF-8和UTF-16的区别?

区分UTF-8还是UTF-16的文件:文件头包含

EF BB BF    UTF-8

FE FF     UTF-16/UCS-2, little endian

FF FE     UTF-16/UCS-2, big endian

FF FE 00 00  UTF-32/UCS-4, little endian.

00 00 FE FF  UTF-32/UCS-4, big-endian.

UTF-16不需要用啥字符来做标志,所以两字节也就是2的16次能表示65536个字符.

 表示一个"汉"中文字(27721(十进制)), UTF-16 是 01101100 01001001; 十六进制是 6C49

UTF-8由于里面有额外的标志信息,所有一个字节只能表示2的7次方128个字符,两个字节只能表示2的11次方2048个字符.而三个字节能表示2的16次方,65536个字符.

所以UTF-8对"汉"的编码是 11100110 10110001 10001001(这里不关注大小端的问题,大小端不同,编码顺序不同)

由上面我们可以看出UTF-8需要判断每个字节中的开头标志信息,所以如果一当某个字节在传送过程中出错了,就会导致后面的字节也会解析出错.而UTF-16不会判断开头标志,即使错也只会错一个字符,所以容错能力强.

 

 

大小端问题:

计算机是以字节为寻址单位的,这就涉及到字(2个字节), 双字(4个字节)及其他多字节单位 在计算机内如何排布的问题, 这里无非就是2种:低字节在低地址的little-endian和高字节在低地址的big-endian.

如何区分当前系统是哪种类型的大小端? 曾经看到有经验的程序员也以当前的操作系统类型来判断, 实际上系统的大小端和你的CPU架构体系相关联, 比如说X86是小端, PowPC是大端,ARM则是可控制(默认也是小端)。

要判断当前环境是大小端实际上很简单: bool IsLittleEndian()  {  int i=1;  return (*(char *)&i == 1); }

感觉现在大端的应用主要是网络字节序, Java内部全都是大端。

UCS2和UCS4因为都是用多字节表示一个字符,所以实际上都有大小端的问题,比如分别对应UCS2-LE和UCS2-BE,Windows上的UNICODE实际上是UCS2-LE;

UTF8因为是字节流,所以没有大小端的问题。

 

 

base64 编码:

Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045~RFC2049,上面有MIME的详细规范。Base64编码可用于在HTTP环境下传递较长的标识信息。

因为,Base64将三个字节转化成四个字节,因此Base64编码后的文本,会比原文本大出三分之一左右。

举一个具体的实例,演示英语单词Man如何转成Base64编码。

Text content

M

a

n

ASCII

77

97

110

Bit pattern

0

1

0

0

1

1

0

1

0

1

1

0

0

0

0

1

0

1

1

0

1

1

1

0

Index

19

22

5

46

Base64-Encoded

T

W

F

u

 

第一步,"M"、"a"、"n"的ASCII值分别是77、97、110,对应的二进制值是01001101、01100001、01101110,将它们连成一个24位的二进制字符串010011010110000101101110。

第二步,将这个24位的二进制字符串分成4组,每组6个二进制位:010011、010110、000101、101110。

第三步,在每组前面加两个00,扩展成32个二进制位,即四个字节:00010011、00010110、00000101、00101110。它们的十进制值分别是19、22、5、46。

第四步,根据上表,得到每个值对应Base64编码,即T、W、F、u。

 

如果字节数不足三,则这样处理:

a)二个字节的情况:将这二个字节的一共16个二进制位,按照上面的规则,转成三组,最后一组除了前面加两个0以外,后面也要加两个0。这样得到一个三位的Base64编码,再在末尾补上一个"="号。

比如,"Ma"这个字符串是两个字节,可以转化成三组00010011、00010110、00010000以后,对应Base64值分别为T、W、E,再补上一个"="号,因此"Ma"的Base64编码就是TWE=。

b)一个字节的情况:将这一个字节的8个二进制位,按照上面的规则转成二组,最后一组除了前面加二个0以外,后面再加4个0。这样得到一个二位的Base64编码,再在末尾补上两个"="号。

比如,"M"这个字母是一个字节,可以转化为二组00010011、00010000,对应的Base64值分别为T、Q,再补上二个"="号,因此"M"的Base64编码就是TQ==。

 

实例函数:

static const uint8_t *kBase64EncodeTable = (const uint8_t *)

  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

 

void  base64_encode(const uint8_t *in, uint32_t len, uint8_t *buf) {

  buf[0] = kBase64EncodeTable[(in[0] >> 2) & 0x3F];

  if (len == 3) {

    buf[1] = kBase64EncodeTable[((in[0] << 4) + (in[1] >> 4)) & 0x3f];

    buf[2] = kBase64EncodeTable[((in[1] << 2) + (in[2] >> 6)) & 0x3f];

    buf[3] = kBase64EncodeTable[in[2] & 0x3f];

  } else if (len == 2) {

    buf[1] = kBase64EncodeTable[((in[0] << 4) + (in[1] >> 4)) & 0x3f];

    buf[2] = kBase64EncodeTable[(in[1] << 2) & 0x3f];

  } else  { // len == 1

    buf[1] = kBase64EncodeTable[(in[0] << 4) & 0x3f];

  }

}

 

static const uint8_t kBase64DecodeTable[256] ={

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,

  52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,

  -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,

  15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,

  -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,

  41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,

};

 

void base64_decode(uint8_t *buf, uint32_t len) {

  buf[0] = (kBase64DecodeTable[buf[0]] << 2) |

           (kBase64DecodeTable[buf[1]] >> 4);

  if (len > 2) {

    buf[1] = ((kBase64DecodeTable[buf[1]] << 4) & 0xf0) |

              (kBase64DecodeTable[buf[2]] >> 2);

    if (len > 3) {

      buf[2] = ((kBase64DecodeTable[buf[2]] << 6) & 0xc0) |

                (kBase64DecodeTable[buf[3]]);

    }

  }

}

 

 

 

 

多字符与双字符转码函数:

Windows上不同编码方式的相互转换可以通过WideCharToMultiByte和MutiByteToWideChar进行转码。

linux下可以用mbstowcs() ,wcstombs() 或者使用iconv库函数;

需要注意的问题:

size_t mbstowcs(wchar_t *wcstr,const char *mbstr,size_t count);

这个函数的第三个参数count,大小一定要是mbstr长度的2倍,否则出来的中文也会是乱码。

iconv库函数的使用

#include <iconv.h>

iconv_t iconv_open(const char *tocode, const char *fromcode);

size_t iconv(iconv_t cd,char **inbuf,size_t *inbytesleft,char **outbuf,size_t *outbytesleft);

int iconv_close(iconv_t cd);

iconv函数的使用需要setlocale() 这个函数设置环境:如:setlocale(LC_ALL,"zh_CN.GB18030");

 

linux下的库函数使用麻烦而且易出错,可以使用下列函数

// 检查字符串中是不是有需要转换的宽字符(utf-8编码中的宽字符),当然该函数不能识别是不是unicode编码,只能识别中文等等多字节字符的特征

bool CheckIsUTF8(const char *put_in, int size)

{

if (put_in != NULL && size-- > 0)  // size减1预先去掉结尾位置,所有size因为是实际长度+1

{

while (*put_in != 0 && size > 0) // 遍历

{

unsigned char ch = *put_in;

if (ch > 0x7FU)  // 检查是不是utf8编码,是的话一次取完,(UTF-8没有大小端的问题)

{

int cwch = 0;  // 记录个数

while (ch & 0x80U)

{

ch <<= 1;  

cwch++;

}

if (cwch > 6 || cwch < 1)  // 超过了最大识别边界

{

put_in++;

size--;

continue;

}

unsigned char ch_g = *put_in++;

size--;

bool isTrue = true;

while (--cwch > 0 && *put_in != 0 && size > 0)  // 递减验证

{

if (0x80U != ch_g & 0x80U)  // 表示没有

{

isTrue = false;

}

ch_g = *put_in++;

size--;

}

if (isTrue)

{

return true; // 表示包含宽字符编码

}

}

else

{

put_in++;

size--;

}

}

}

int UTF8ToUTF16(const char *pmbs, wchar_t *pwcs, int size)  // pmbs需要有边界以0结尾,size表示字符个数

{

int cnt = 0;

if (pmbs != NULL && pwcs != NULL && size-- > 0)  // size减1预先去掉结尾位置,所有size因为是实际长度+1

{

while (*pmbs != 0 && size > 0) // 遍历

{

unsigned char ch = *pmbs;

//printf("%c %x\n", ch,ch);

if (ch > 0x7FU)  // 检查是不是utf8编码,是的话一次取完,(UTF-8没有大小端的问题)

{

int cwch = 0;  // 记录个数

while (ch & 0x80U) // 检查字符头是不是宽字符标记

{

ch <<= 1;  // 左移一位,检查有几个字符

cwch++;

}

if (cwch > size) // 检查是不是最后几个字符

{

*pwcs = *pmbs++;

//printf("1\n");

//printf("1: %ls \n", pwcs);

}

else

{

*pwcs = *pmbs++ & (0xFFU >> cwch);  // 付给第一个字符的数据值

size--;

while (--cwch > 0)  // 递减,(没有做严格验证)

{

if (size <= 0)

{

*pwcs = *pmbs;  // 重复复制,保证单值正确

}

else

{

*pwcs <<= 6; // 向大端平移6位(因为跟随字符都是6位有效)

*pwcs |= (*pmbs++ & 0x3FU); // 添加地位字符

size--;

}

}

//printf("2 \n");

//printf("2:%ls \n", pwcs);

}

}

else

{

*pwcs = *pmbs++;   // 表示是单字节编码,直接赋值给一个宽字符(自动适应大小端)

size--;

}

//printf("3 \n");

pwcs++;

cnt++;

}

*pwcs = 0; // 以0x00

cnt++;

}

return cnt;

}

 

 

int UnicodeToUTF8(const wchar_t *pwcs, char *pmbs, int size)

{

int cnt = 0;

// 这里 size-- 是预先除去尾零所需位置

if (pwcs != NULL && pmbs != NULL && size-- > 0) {

while (*pwcs != 0 && size > 0) {

if (*pwcs < 0x00000080U) {

*pmbs++ = (char)*pwcs;

size -= 1;

cnt += 1;

}

else if (*pwcs < 0x00000800U) {

// 剩余空间不够存放该字符

if (size < 2) {

break;

}

*pmbs++ = (0xFFU << 6) | (*pwcs >> 6);

*pmbs++ = 0x80U | (*pwcs & 0x3FU);

size -= 2;

cnt += 2;

}

else if (*pwcs < 0x00010000U) {

// 剩余空间不够存放该字符

if (size < 3) {

break;

}

*pmbs++ = (0xFFU << 5) | (*pwcs >> 12);

*pmbs++ = 0x80U | ((*pwcs >> 6) & 0x3FU);

*pmbs++ = 0x80U | (*pwcs & 0x3FU);

size -= 3;

cnt += 3;

}

else if (*pwcs < 0x00200000U) {

// 剩余空间不够存放该字符

if (size < 4) {

break;

}

*pmbs++ = (0xFFU << 4) | (*pwcs >> 18);

*pmbs++ = 0x80U | ((*pwcs >> 12) & 0x3FU);

*pmbs++ = 0x80U | ((*pwcs >> 6) & 0x3FU);

*pmbs++ = 0x80U | (*pwcs & 0x3FU);

size -= 4;

cnt += 4;

}

else if (*pwcs < 0x04000000U) {

// 剩余空间不够存放该字符

if (size < 5) {

break;

}

*pmbs++ = (0xFFU << 3) | (*pwcs >> 24);

*pmbs++ = 0x80U | ((*pwcs >> 18) & 0x3FU);

*pmbs++ = 0x80U | ((*pwcs >> 12) & 0x3FU);

*pmbs++ = 0x80U | ((*pwcs >> 6) & 0x3FU);

*pmbs++ = 0x80U | (*pwcs & 0x3FU);

size -= 5;

cnt += 5;

}

else if (*pwcs < 0x80000000U) {

// 剩余空间不够存放该字符

if (size < 6) {

break;

}

*pmbs++ = (0xFFU << 2) | (*pwcs >> 30);

*pmbs++ = 0x80U | ((*pwcs >> 24) & 0x3FU);

*pmbs++ = 0x80U | ((*pwcs >> 18) & 0x3FU);

*pmbs++ = 0x80U | ((*pwcs >> 12) & 0x3FU);

*pmbs++ = 0x80U | ((*pwcs >> 6) & 0x3FU);

*pmbs++ = 0x80U | (*pwcs & 0x3FU);

size -= 6;

cnt += 6;

}

else {

// 无法识别的 Unicode 字符

break;

}

pwcs++;

}

*pmbs = 0;

cnt++;

}

return cnt;

}

0 0
原创粉丝点击