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;
}
- c++转码基础(1):各种编码类型及unicode和uft-8互转
- 字符集Unicode和UFT-8
- 编码认识(GBK,GB2312,GB18030,US-ASCII,Unicode,UFT-8,UFT-16,UFT-32)
- UFT-8转本地化编码
- 各种编码Unicode UTF-8 GBK 及一点Java代码
- char类型和Unicode编码
- UFT-ASCII-GB2312-Unicode编码转换
- UFT-8,多字节和UNICODE之间的转换
- UFT-8,多字节和UNICODE之间的转换
- UFT-8转本地化编码wenhai
- delphi下中文转UFT-8编码
- uft-8编码识别
- UTF-8转Unicode 编码 C语言
- unicode utf-8 gb18030 gb2312 gbk各种编码对比 [转]
- 解析Unicode编码和Java char 类型
- java中的char类型和Unicode编码
- 解析Unicode编码和Java char 类型
- java中的char类型和Unicode编码
- 数据库连接及增删改查那些事
- Java transient关键字
- DAG上的动态规划
- 为了无法计算的价值【阿里云数据可视化】笔记
- 插入排序-《算法导论》学习笔记一
- c++转码基础(1):各种编码类型及unicode和uft-8互转
- 一套完整的Android通用框架
- 关于python实现把文件提取出来写到excel表里
- Fio压测工具和io队列深度理解和误区
- 浙大PAT甲级 1113
- 第32篇 网站试题生成word下载时bug解决
- 项目使用第三方插件发送短信-------建周短信SDK(Software Development Kit)
- codeforces基础题——#358(div2)C
- CCF 201512-2 消除类游戏(水)