C/C++ 实现十六进制面值转字符串、字符面值转十六进制、UNICODE与GBK互转,UTF-8与GBK互转
(1)ASCII码
ASCII码一共规定了128个字符的编码,比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。由此,当LCD显示的时候,我们就可以根据是不是大于0X80来区分是中文字符还是ASCII字符。
可显示ASCII字符范围如下,一共96个:
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
(2)非ASCII码
英语用128个符号编码就够了,但是用来表示其他语言128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示;还有一个更重要的问题就是每个国家的文字个数都不一样!
简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256x256=65536个符号;GBK是GB2312的扩展字库,能够显示的汉字更多了。
(3)Unicode码表
由于每个国家的文字编码格式都不尽相同,所以当打开一个文件的时候就得知道这个文件的编码格式,否则打开后就是显示的乱码。由此,Unicode码表应运而生,它是一个很大的文字映射集合,将全球所有国家的文字全部编码进去了,每一个文字都有一个唯一的编号,现在的规模可以容纳100多万个符号。所以,unicode码表就是各个国家字符相互转换的媒介!
(4)UTF-8编码
好多人分不清unicode和UTF-8是什么关系,我这里简单的说一下。unicode只是一个映射表,而UTF-8是unicode码的存储方式。
我们都耳熟能详的知道ASCII字符为1个字节,中文汉字为2个字节,但是其他国家文字可能需要3个或者4个字节才能表示一个他们国家的文字,这里就出现了严重的问题:
- 怎样区分ASCII和unicode,计算机怎么才能知道到底是用几个字节表示一个文字?
- 如果unicode统一规定每个文字用3或4个字节表示,那么对于ASCII来说它就只是一个字节的大小,严重浪费空间。
UTF8解决的问题
现在有用户A是法国的,用户B是中国的,那么显然两个国家的文字不一样,现在B向A通过网络传输数据,那么流程是这样:
- B首先要确定A是哪一国家的(否则无法将A国的文字转为本地语言)
- A发送数据
- B接收数据
- B将接收到的数据转成本地语言
- B查看数据
那么现在问题就来了,如果接收方不知道发送方是哪一国家的,那么接收到的数据就没办法转成本地语言来显示!
那怎么办呢?
也简单,如果说所有国家在进行网络传输的时候都按照规定使用同一种编码进行传输,那么上述流程的第一步不就省略了嘛!由此,UTF-8码就出现了。
(上述举例可能不是很准确,但是意思在这了)。
目前UTF-8码在互联网上是使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过现在用的很少。
UTF-8最大的一个特点,就是它是一种变长的编码方式。使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的两条编码规则如下:
- 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
- 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表是编码规则,字母X表示可用编码位:
UNICODE符号范围(十六进制) | UTF-8编码方式(二进制) |
0X00000000~0X0000007F | 0xxxxxxx |
0X00000080~0X000007FF | 110xxxxx 10xxxxxx |
0X00000800~0X0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0X00010000~0X0010FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
下面用汉字【你】来演示说明UTF-8的编码规则。
【你】这个汉字的unicode码是0X4F60,在0X00000800~0X0000FFFF范围内,
0X4F60二进制为【0100,1111,0110,0000】,
将这16位依次填入【1110xxxx 10xxxxxx 10xxxxxx】的x位置,
得到【11100100
10111101
10100000
】
转为十六进制:0XE4BDA0
所以【你】这个汉字的UTF-8编码就是0XE4BDA0
你可以使用这个网站验证一下是否正确:https://www.qqxiuzi.cn/bianma/Unicode-UTF.php
(5)关于GBK和UTF-8之间的互转
上面已经说到了,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,GBK是GB2312的扩展字库,涵盖的汉字更多了。
GBK与UTF8之间是没有关系的,无法直接转换。但是GBK和UNICODE有关系,因为每一个GBK汉字都在UNICODE表中有一个唯一的编号,而UTF8和UNICODE可以直接转换,所以GBK转UTF-8是分两步完成的,步骤如下:
- 通过查unicode表获得GBK汉字在unicode码表中的编号
- 将GBK汉字的unicode编号转为UTF-8编码
而UTF-8转GBK就是上述两步的逆向操作。
(6)C实现的编码转换
unicode和gbk之间的码表很大,就不贴源码了,光贴个头文件,源码自取:https://gitee.com/jhuangBTT/textcodec
/*
* textcodec.h
*
* Created on: 2022年12月7日
* Author: [email protected]
*/
#ifndef TEXTCODEC_H_
#define TEXTCODEC_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/**
* 十六进制面值转字符串,例:{0XAA,0XBB,0XCC} -> "AABBCC"
* @param from 待转换的十六进制数据
* @param fromSize 待转换的十六进制数据大小
* @param to 存放转换的字符串
* @param toSize 存放转换的字符串的大小
*/
void HexToStr(const uint8_t* from, uint32_t fromSize, char* to, uint32_t* toSize);
/**
* 十六进制字符串转数值,例:"AABBCC" -> {0XCC,0XBB,0XAA}
* @param from 待转换的十六进制字符串
* @param fromSize 字符串长度
* @param to 存放字符串的十六进制值
* @param toSize 存放字符串的十六进制值的大小
*/
void StrToHex(const char* from, uint32_t fromSize, uint8_t* to, uint32_t* toSize);
/**
* @brief GBK码转UTF8码
* @param from GBK码
* @param fromSize GBK码的大小
* @param to UTF8码
* @param toSize UTF8码的大小
*/
void GBKToUTF8(const uint8_t* from, uint32_t fromSize, uint8_t* to, uint32_t* toSize);
/**
* @brief UTF8码转GBK码
* @param from UTF8码
* @param fromSize UTF8码的大小
* @param to GBK码
* @param toSize GBK码大小
*/
void UTF8ToGBK(const uint8_t* from, uint32_t fromSize, uint8_t* to, uint32_t* toSize);
/**
* @brief GBK码转双字节UNICODE码
* @param from GBK码
* @param fromSize GBK码大小
* @param to UNICODE码
* @param toSize UNICODE码大小
*/
void GBKToUnicode(const uint8_t* from, uint32_t fromSize, uint8_t* to, uint32_t* toSize);
/**
* @brief 双字节UNICODE码转GBK码
* @param from 双字节UNICODE码
* @param fromSize UNICODE码大小
* @param to GBK码
* @param toSize GBK码大小
*/
void UnicodeToGBK(const uint8_t* from, uint32_t fromSize, uint8_t* to, uint32_t* toSize);
/**
* @brief 三字节UTF8码转双字节UNICODE码
* @param from UTF8码
* @param fromSize UTF8码大小
* @param to GBK码
* @param toSize GBK码大小
*/
void UTF8ToUnicode(const uint8_t* from, uint32_t fromSize, uint8_t* to, uint32_t* toSize);
/**
* @brief 双字节UNICODE码转三字节UTF8码
* @param from
* @param fromSize
* @param to
* @param toSize
*/
void UnicodeToUTF8(const uint8_t* from, uint32_t fromSize, uint8_t* to, uint32_t* toSize);
#ifdef __cplusplus
}
#endif
#endif /* TEXTCODEC_H_ */
测试程序:
/*
* main.c
*
* Created on: 2022年12月7日
* Author: hello
*/
#include "textcodec.h"
#include <string.h>
#include <stdio.h>
int main()
{
const char* text = "你好ABC世界"; /* 注意:当前文件的编码格式为UTF-8 */
uint32_t utf8Size, unicodeSize, gbkSize;
uint8_t utf8[64];
uint8_t unicode[64];
uint8_t gbk[64];
//
// 打印原始数据:本文件是UTF-8编码的
//
printf(" ------ byte array in UTF8 format: ");
for(int i = 0; i < strlen(text); i++)
{
printf("0x%02X,", text[i] & 0XFF);
}
printf("\n");
//
// UTF8转UNICODE
//
printf(" ------ utf8 to unicode : ");
UTF8ToUnicode((const uint8_t *)text, strlen(text), unicode, &unicodeSize);
for(int i = 0; i < unicodeSize; i++)
{
printf("0x%02X,", unicode[i] & 0XFF);
}
printf("\n");
//
// UTF8转GBK
//
UTF8ToGBK((const uint8_t *)text, strlen(text), gbk, &gbkSize);
printf(" ------ utf8 to gbk : ");
for(int i = 0; i < gbkSize; i++)
{
printf("0x%02X,", gbk[i] & 0XFF);
}
printf("\n");
//
// GBK转UNICODE
//
printf(" ------ gbk to unicode : ");
GBKToUnicode((const uint8_t *)gbk, gbkSize, unicode, &unicodeSize);
for(int i = 0; i < unicodeSize; i++)
{
printf("0x%02X,", unicode[i] & 0XFF);
}
printf("\n");
//
// GBK转UTF8
//
GBKToUTF8(gbk, gbkSize, utf8, &utf8Size);
printf(" ------ gbk to utf8 : ");
for(int i = 0; i < utf8Size; i++)
{
printf("0x%02X,", utf8[i] & 0XFF);
}
printf("\n");
//
// UNICODE转UTF8
//
UnicodeToUTF8(unicode, unicodeSize, utf8, &utf8Size);
printf(" ------ unicode to utf8 : ");
for(int i = 0; i < utf8Size; i++)
{
printf("0x%02X,", utf8[i] & 0XFF);
}
printf("\n");
//
// unicode转gbk
//
UnicodeToGBK(unicode, unicodeSize, gbk, &gbkSize);
printf(" ------ unicode to gbk : ");
for(int i = 0; i < gbkSize; i++)
{
printf("0x%02X,", gbk[i] & 0XFF);
}
printf("\n");
//
// HEX转STR
//
uint8_t hex[3] = {0XAA, 0XBB, 0XCC};
uint32_t hexSize;
char str[32];
uint32_t strSize;
HexToStr(hex, 3, str, &strSize);
printf(" ------ HEX TO STR: %s \n", str);
//
// STR转HEX
//
StrToHex(str, strSize, hex, &hexSize);
printf(" ------ STR TO HEX: 0x%02X,0x%02X,0x%02X \n", hex[0], hex[1], hex[2]);
return 0;
}
运行结果:
------ byte array in UTF8 format: 0xE4,0xBD,0xA0,0xE5,0xA5,0xBD,0x41,0x42,0x43,0xE4,0xB8,0x96,0xE7,0x95,0x8C,
------ utf8 to unicode : 0x60,0x4F,0x7D,0x59,0x41,0x00,0x42,0x00,0x43,0x00,0x16,0x4E,0x4C,0x75,
------ utf8 to gbk : 0xC4,0xE3,0xBA,0xC3,0x41,0x42,0x43,0xCA,0xC0,0xBD,0xE7,
------ gbk to unicode : 0x60,0x4F,0x7D,0x59,0x41,0x00,0x42,0x00,0x43,0x00,0x16,0x4E,0x4C,0x75,
------ gbk to utf8 : 0xE4,0xBD,0xA0,0xE5,0xA5,0xBD,0x41,0x42,0x43,0xE4,0xB8,0x96,0xE7,0x95,0x8C,
------ unicode to utf8 : 0xE4,0xBD,0xA0,0xE5,0xA5,0xBD,0x41,0x42,0x43,0xE4,0xB8,0x96,0xE7,0x95,0x8C,
------ unicode to gbk : 0xC4,0xE3,0xBA,0xC3,0x41,0x42,0x43,0xCA,0xC0,0xBD,0xE7,
------ HEX TO STR: AABBCC
------ STR TO HEX: 0xAA,0xBB,0xCC
ends…