什么是编码格式

从一个小问题引入

我们在学习C语言的时候,有一道必做的题目是将大写字母转换成小写,相信有点基础的同学都能不加思索的写出下面的代码:char toLower(char upper){

if (upper >= 'A' && upper <= 'Z'){

return upper + 32;

}else{

return upper;

}

}

要问为什么是这段代码?我们往往也能说得出:因为大小写字母在ASCII码上正好相差32(字符'a'为97, 字符'A'为65)。

我们在进行字符初始化的时候,往往会将字符初始化为'\0'。因为'\0'在ASCII码中对应的数值是0。

我们理所应当地知道char型字符对应的范围是0~127,因为ASCII码的范围就是0~127。

但是有没有想过,为什么是ASCII码?

所谓的ASCII码,又到底是什么?

编码格式介绍

要说起ASCII码,不得不说起编码格式。

我们知道,对于计算机来说,我们在屏幕上看到的千姿百态的文字、图片、甚至视频是不能直接识别的,而是要通过某种方式转换为0和1组成的二进制的机器码,最终被计算机识别(0为低电平,1为高电平)。

对于数字来说,有一套非常成熟的转换方案,就是将十进制的数字转换为二进制,就能直接被计算机识别(如5转换为二进制是 0000 0101)。但是对于像ABCD这样的英文字母,还有!@#$这样的特殊符号,计算机是不能直接识别的,所以就需要有一套通用的标准来进行规范。

这套规范就是ASCII码。

ASCII码使用127个字符,表示A~Z等26个大小写字母,包含数字0~9,所有标点符号以及特殊字符,甚至还有不能在屏幕上直接看到的比如回车、换行、ESC等。

按照这套SACII的编码标准,就很容易的知道,'\0'代表的是0, 'A'代表的是65,而'a'代表的是97,'A'和'a'之间正好相差了32。

ASCII码虽然只有127位,但基本实现了对所有英文的支持。所以为什么说char类型只占1个字节?因为char型最大的数字是127,转成二进制也不过是0111 1111,只需要1个字节就能表示所有的char型字符,因此char只占1个字节。

但是随着计算机的普及,计算机不但要处理英文,还有汉字、甚至希腊文字、韩文、日文等诸多文字,这时,127个字符肯定不够了,这时就引入了Unicode的概念。

Unicode是一个编码字符集,它基本涵盖了世界上绝大多数的文字(只有极少数没有包含),在Unicode中文对照表中可以查看一些汉字的Unicode字符集。

比如,汉字”七“在Unicode表示为十六进制0x4e03,表示成二进制位0100 1110 0000 0011,占了15位,至少需要两个字节才能放得下,有些更复杂的生僻字,可能占用的字节数甚至不止两位。

这就面临着一个问题,当一个中英文夹杂的字符串输入到电脑的时候,计算机是如何知道它到底是什么的?

就像上面的0100 1110 0000 0011,它到底是表示的是0100 1110和0000 0011两个ASCII字符,还是汉字”七“?计算机并不知道。所以就需要一套规则来告诉计算机,到底该按照什么来解析。这些规则,就是字符编码格式。

其中就包括以下几种。ASCII

UTF-8

GBK

GB2312

GB18030

BIG5

ISO8859

编码格式分类

ASCII

ASCII 编码前面已经介绍过,此处就不再多说了。它使用0~127这128位数字代表了所有的英文字母以及数字、标点、特殊符号和键盘上有但屏幕上看不见的特殊按键。

它的优点是仅用128个数字就实现了对英文的完美支持,但是缺点也同样明显,不支持中文等除英文以外的其他语言文字。

因此,ASCII码基本可以看做是其他字符编码格式的一个子集,其他字符编码都是在ASCII码的基础上实现了一定的扩展,但毫无意外地,都实现了对ASCII码的兼容。

UTF-8

在汉字环境下,UTF-8可以说是最常见的编码。它是Windows系统默认的文本编码格式。

UTF-8是一种变长的编码方式,最大可以支持到6位。这就意味着他可以有效地节省空间(在后面介绍GBK的时候,会讲GBK是固定长度的编码方式)。

那么,UTF8是如何知道当前所要表达的字符是几个字节呢?

在UTF8中,它以首字节的高位作为标识,用来区别当前字节的长度。其规则大致如下:1字节 0xxxxxxx (范围:0x00-0x7F)

2字节 110xxxxx 10xxxxxx (范围:0x80-0x7ff)

3字节 1110xxxx 10xxxxxx 10xxxxxx (范围:0x800-0xffff)

4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (范围:0x10000-0x10ffff)

5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

如上面的汉字”七“的unicode码是0x4e03,在0x800-0xffff区间,所以是3字节,用UTF-8表示就是11100100 10111000 10000011(十六进制表示为0xe4b883)。

"七"的Unicode码是0100 1110 0000 0011,可为什么是这个数呢?

根据3字节的填充规则,从右往左,依次填充x的位置:0100     111000     000011

+

1110xxxx 10xxxxxx 10xxxxxx

=

11100100 10111000 10000011

事实上,utf-8编码下,汉字都为3字节。

实际上UTF家族除了UTF-8外,还有UTF-16、UTF-32等,由于不太常用,此处也就不展开讨论了。

GBK/GB2312/GB18030

GB就是”国标“的拼音开头,顾名思义,以GB开头的编码都是中国人专门为支持汉语而设计的编码格式。但这三者又有区别,最早出现的是GB2312,它收录了6763个汉字,基本满足了计算机对汉字的处理需要。

GB2312使用双字节表示一个汉字。对汉字进行分区处理。每个区含有94个汉字(或符号),这种表示方式称之为区位码。01-09 区为特殊符号。

16-55 区为一级汉字,按拼音排序。

56-87 区为二级汉字,按部首/笔画排序。

10-15 区及 88-94 区则未有编码。

GB2312编码范围:A1A1-FEFE,其中汉字编码范围:B0A1-F7FE。表示汉字时,第一字节0xB0-0xF7(对应区号:16-87),第二个字节0xA1-0xFE(对应位号:01-94)。

GBK是在GB2312基础上的扩展。GBK的K就是扩展的”扩“的拼音首字母。因此,GBK向下兼容GB2312。

GBK也使用双字节表示汉字,其中首字节范围0x81-0xfe,第二个字节范围0x40-0xfe,剔除0x7F一条线。因此,GBK所能表示的汉字比GB2312要多得多(能表示21886个汉字)。

GB18030是最新的内码字集,可以表示70244个汉字。它与UTF-8类似,采用多字节编码,每个汉字由1、2、4个字节组成。单字节,其值从 0 到 0x7F,与 ASCII 编码兼容。

双字节,第一个字节的值从 0x81 到 0xFE,第二个字节的值从 0x40 到 0xFE(不包括0x7F),与 GBK 标准兼容。

四字节,第一个字节的值从 0x81 到 0xFE,第二个字节的值从 0x30 到 0x39,第三个字节从0x81 到 0xFE,第四个字节从 0x30 到 0x39。

如果你看到这个地方已经觉得很乱了,不要紧。我们只需要知道,在GB打头的编码格式下,我们能够用键盘敲出来的,你在电脑上所看见的所有汉字,都是双字节的(四字节的汉字极少,只有一些极少数不常用的生僻字用到)。

BIG5

BIG5,从字面翻译来看,叫做”大五码“,它主要用来表示中文繁体字。

它也是用双字节表示一个汉字,其中高位字节使用了0x81-0xFE,低位字节使用了0x40-0x7E,及0xA1-0xFE。。

这种编码格式用的比较少,此处就不展开说了。

汉字编码

上面介绍的几种编码格式,UTF-8、GBK等都支持汉字,但是标准不同,因此,在实际进行开发的过程中,对汉字的处理也不尽相同。

如何判断汉字编码

无论是UTF-8、GBK,还是GB18030,或者BIG5,它都是向下兼容ASCII的,为了区分ASCII码和汉字,在汉字的高位补1。

这也就是说,如果我们以int的形式取出单个字符的值,汉字都是小于0的。

因此,判断是否是汉字也就变得简单了:enum boolean{true, false};

typedef int boolean;

boolean isChinese(char ch){

return (ch < 0) ? true : false;

}

写一段代码验证一下:void test01(){

char str[20];

memset(str, 0, sizeof(str));

strcpy(str, "hello汉字");

for (int i = 0; i < strlen(str); i++){

if (isChinese(str[i]) == true){

printf("str[%d]: Chinese\n", i);

}else{

printf("str[%d]: English\n", i);

}

}

}

我们在main函数里调用test01函数,得到如下结果:

因为在utf-8下,一个汉字占3字节,所以后面从5~10这6个字节正好代表着2个汉字。

如果我们把编码改成GB2312,运行可以得到如下结果:

可以看到,只有最后4个字节是汉字,充分说明了GB2312编码格式下,一个汉字占2个字节。

如何处理汉字截断问题

如果我们把上面的字符串按字符打印出来,得到下面的结果:

可以看到,所有的汉字都乱码了,原因就在于,UTF-8编码下,每个汉字占3个字节,一个字节不足以表示完整的汉字,所以打印出来都是乱码的。

在实际开发中,比较常见的需要处理的问题是,截取一定长度的字符串,但是如果截取的位置正好是个汉字,难免会遇到汉字被截断的问题。

那么,这类问题如何处理呢?

根据汉字的编码规则,我们知道,UTF-8和GBK对汉字的处理是不一样的。

UFT-8一个汉字是3字节,且规则如下:1110xxxx 10xxxxxx 10xxxxxx

所以,我们很容易知道,汉字的首字节范围为11100000~11101111,转成十六进制为0xe0~0xef,第二、三字节的范围为10000000~10111111,转成十六进制范围为0x80~0xbf。

所以UTF-8的汉字截断问题处理可以如下:void HalfChinese_UTF8(const char *input, size_t input_len, char *output, size_t *output_len)

{

char current = *(input + input_len);

if (isChinese(current) == false)

{

*output_len = input_len;

strncpy(output, input, *output_len);

return;

}

//汉字

*output_len = input_len;

//1110xxxx 10xxxxxx 10xxxxxx

//第二位和第三位的范围是10000000~10ffffff,转成十六进制是0x80~0xbf,在这个范围内都说明是汉字被截断

while ((current&0xff) < 0xc0 && (current&0xff) >= 0x80)

{

(*output_len)++;

current = *(input + *output_len);

}

strncpy(output, input, *output_len);

}

该函数有四个参数,其中input和input_len作为原始输入,input_len代表需要截取的位置,output和output_len作为输出,output为截断处理后的字符串,output_len为截断处理后的长度。

我们使用下面的代码进行测试:void test02()

{

char in[20], out[20];

memset(in, 0, sizeof(in));

memset(out, 0, sizeof(out));

strcpy(in, "hello汉字");

size_t out_len = 0;

for (int i = 1; i <= strlen(in); i++)

{

HalfChinese_UTF8(in, i, out, &out_len);

printf("out: %s\n", out);

}

}

运行后结果如下:

如果是GBK编码,要稍微麻烦一点。因为我们知道,GBK是双字节表示汉字,且第一个字节的值从 0x81 到 0xFE,第二个字节的值从 0x40 到 0xFE(不包括0x7F),单从字符的值无法判断到底是汉字的首字节还是后一个字节(因为二者的值有重复部分)。

如果字符串纯为汉字倒还好办,我们已经知道汉字占2个字节,直接根据长度的奇偶来判断就可以,但如果是中英文夹杂就不能采用这种方式了。

在这里,我使用的是先对字符串进行一道过滤处理,判断字符串中除掉英文字符后纯汉字的长度,如果为奇数,代表汉字被截断,加1就能取其完整的汉字,如果是偶数,说明正好是一个完整的汉字,无需处理,直接返回即可。

代码实现如下:void HalfChinese_GBK(const char *input, size_t input_len, char *output, size_t *output_len){

char current = *(input + input_len);

if (isChinese(current) == false)

{

*output_len = input_len;

strncpy(output, input, *output_len);

return;

}

*output_len = input_len;

if (MoveEnglish(input, input_len) %2 != 0){

(*output_len)++;

}

strncpy(output, input, *output_len);

}

int MoveEnglish(const char *input, size_t input_len){

int out_len = input_len;

for (int i = 0; i < input_len; i++)

{

if (isChinese(input[i]) == false){

out_len++;

}

}

return (out_len > 0) ? out_len : 0;

}

同样使用上面的测试代码进行测试,得到如下结果:

如何实现编码之间互相转换

既然编码格式这么多,那么怎么进行编码之间的转换呢?

在C语言下,主要是利用系统的iconv函数完成。

iconv函数包含在头文件iconv.h中,其函数原型如下所示:size_t iconv (iconv_t __cd, char **__restrict __inbuf,

size_t *__restrict __inbytesleft,

char **__restrict __outbuf,

size_t *__restrict __outbytesleft);

第一个参数是转换的一个句柄,由iconv_open函数创建,第二个参数是输入的字符串,第三个参数是输入字符串的长度,第四个参数是转换后的输出字符串,第五个参数是输出字符串的长度。在编码转换完成之后,需要调用iconv_close函数关闭句柄。所以完整的调用顺序为:iconv_open打开iconv句柄

调用iconv进行编码转换

iconv_close关闭句柄

还有一点需要注意的是,__inbytesleft和__outbytesleft的长度,因为不同编码对于汉字的处理字节数不同,比如从UTF-8转换为GBK,同样都是两个汉字,转换前长度为6,转换后长度为4。也就是说,在编码转换过程中,字符串可能会变长或缩短,如果长度不正确,很容易造成越界,从而导致错误。

完整的编码转换功能封装如下:boolean convert_encoding(char *in, size_t in_len, char *out, size_t out_len, const char *from, const char *to)

{

if (strcasecmp(from, to) == 0){

size_t len = (in_len < out_len) ? in_len : out_len;

memcpy(out, in, len);

return true;

}

iconv_t cd = iconv_open(from, to);

if (cd == (iconv_t)-1){

printf("iconvopen err\n");

return false;

}

size_t inbytesleft = in_len;

size_t outbytesleft = out_len;

char *src = in;

char *dst = out;

size_t nconv;

nconv = iconv(cd, &src, &inbytesleft, &dst, &outbytesleft);

if (nconv == (size_t)-1){

if (errno == EINVAL){

printf("EINVAL\n");

} else {

printf("error:%d\n", errno);

}

}

iconv_close(cd);

return true;

}

注意,由于使用到了libiconv,编译时需要加-liconv进行链接。

测试代码如下:void test04()

{

char in[20], out[20];

memset(in, 0, sizeof(in));

memset(out, 0, sizeof(out));

strcpy(in, "hello汉字world");

if (false == convert_encoding(in, strlen(in), out, 20, "utf-8", "gbk")){

printf("failed\n");

return;

}

printf("in: %s\nout:%s\n", in, out);

}

以上代码运行结果如下所示:

将GBK转换为UTF-8也是同样的操作,此处就不做演示了。

c语言 乱码转化为16进制_编码格式介绍及C语言处理汉字编码相关推荐

  1. c语言 乱码转化为16进制_面向小白的C语言科普教程(一)文件和扩展名、编码和十六进制...

    前言 本系列文章偏向科普向,出发点是因为某乎每天都给我推送有关C语言基础的低质问题,答得多了发现不仅收不到赞,甚至会因为太弱智而掉粉.所以萌生了尝试给有想要了解.学习C语言及相关计算机知识的同学复制一 ...

  2. c语言 乱码转化为16进制_C语言版的16进制与字符串互转函数

    http://www.cnblogs.com/nio-nio/p/3309367.html /* // C prototype : void StrToHex(BYTE *pbDest, BYTE * ...

  3. c语言长整数转化为16进制字符串,一个30位的字符串十进制长整数怎么转换为对应的十六进制和八进制...

    C/C++ code#include #include using namespace std; inline int compare(string str1,string str2) {//相等返回 ...

  4. Bailian2798 2进制转化为16进制【进制】

    2进制转化为3进制 描述 输入一个2进制的数,要求输出该2进制数的16进制表示. 在16进制的表示中,A-F表示10-15 输入 第1行是测试数据的组数n,后面跟着n行输入.每组测试数据占1行,包括一 ...

  5. java字节数组转换成16进制_Java 将字节数组转化为16进制的多种方案

    很多时候我们需要将字节数组转化为16进制字符串来保存,尤其在很多加密的场景中,例如保存密钥等.因为字节数组,除了写入文件或者以二进制的形式写入数据库以外,无法直接转为为字符串,因为字符串结尾有\0,当 ...

  6. 十进制转化为16进制

    有以下种方式实现,其中两种是使用系统函数,另一种是直接自己编写. main() { int u10=0; char u16[10]; int w=0,a,b,i; printf("请输入一个 ...

  7. 进制转化:从键盘接收一个4位的十进制数,将它转化为16进制并输出

    ;从键盘接收一个4位的十进制数,将它转化为16进制并输出 DATA SEGMENT NUM  DW 0 CRLF   DB 0AH,0DH,24H DATA ENDS STACK SEGMENT PA ...

  8. 数组中hashCode就是内存地址,以及汉字幻化为16进制或10进制

    int[] arr4={1,2,3,4,5};System.out.println("arr4: "+arr4); System.out.println("arr4.ha ...

  9. c语言115写成16进制,C语言指针问题

    C语言指针问题 來源:互聯網  2009-04-01 16:01:29  評論 分類: 電腦/網絡 >> 程序設計 >> 其他編程語言 問題描述: int a=115,*p;p ...

最新文章

  1. Udacity机器人软件工程师课程笔记(三十) - 语义分割与实例实现 - 使用keras实现语义分割
  2. Android5.0新特性:主题样式
  3. Apache Superset从入门到放弃(基于python3.6.9)
  4. PHP的JSON封装
  5. html左右滑动选择控件,jQuery让控件左右移动的三种实现方法
  6. 网络工程师交换试验手册之十八:SWITCH的基本配置
  7. css 获取屏幕宽度_设备像素、设备独立像素、CSS像素、分辨率、PPI、devicePixelRatio 的区别...
  8. JAVA入门级教学之(文档里的is-a、is-like-a、has-a到底是什么意思)
  9. Express--socket.io使用session验证
  10. linux摄像头 自动对焦,基于H3在Linux下驱动OV5640摄像头的方法与流程
  11. 2021年Node.js开发人员学习路线图
  12. 一名合格前端工程师的进阶指南!都来认真看一下吧
  13. Devexpress VCL Build v2014 vol 14.2.6 发布
  14. mysql 数据库备份与恢复_mysql 数据库备份与还原
  15. R语言入门1:安装R和RStudio
  16. 【离散数学】集合论 第三章 集合与关系(4) 集合的归纳定义、归纳证明、数学归纳法第一/二原理
  17. 2019年3月PMP考试技巧及答题技巧介绍
  18. python银行系统-python实现简单银行管理系统
  19. 促进社群活跃的几种方法,你get到了吗
  20. 【JAVA】(实训1)EL表达式编码问题

热门文章

  1. poj 2683 Ohgas' Fortune 利率计算
  2. 软文推广丨什么是软文推广?
  3. 电脑上怎么绘制流程图?三分钟快速绘制流程图的秘诀
  4. 计算机入门新人必学,电脑新手入门教程 让你快速上手
  5. day12-HTML、CSS与blog页面讲解
  6. 李飞飞在谷歌开发者大会宣布谷歌AI中国中心正式成立
  7. sqlite3查询表中最后一条记录
  8. #440 科技乱炖:ChatGPT 的惊喜与意料之内
  9. 剑侠世界3怎么快速起号?
  10. rocketMQ的消息介绍、发送方式和消费方式