在开始的学习中,总是把数制和编码搞混,其实,要学会区分二者。
计算机中,处理的都是二进制数,一方面,要符合我们自然的数字习惯,所以就有了十进制;另一方面,因为二进制太长,处于表示方便的目的,所以就有了十六进制(实际中几乎用不到八进制,所以不做讨论)。
但是,不管是什么进制,都是为了表示一个数据。
同一个数据,不管是用二进制表示,还是十进制表示,或者用十六进制表示,在计算机看来,表示的都是同一个数,最终都要转成0101的二进制数,然后控制电路的开和断,从而实现数字信号和模拟信号的结合使用,这块就不展开讨论了。
比如,二进制数01010101,就是十进制的85,也就是十六进制的55,其中十进制是我们的自然习惯,十六进制则更好和二进制转换,更方便使用。计算机处理十进制和十六进制的时候,底层都是转成01010101这个二进制数来存储和传输。
那么,编码是什么呢?
举个例子,我手上有5种水果,苹果、香蕉、梨子、橘子、桃子。因为计算机无法直接表示这几种水果,所以,我就给这几种水果编个号,分别为1、2、3、4、5,当我知道这几种数据是水果类型的时候,当计算机看到1,就知道是苹果,然后就会通过屏幕画一个苹果出来,从宏观来看,我输入了一个1,就得到了一个苹果,这就是编码。不过,实际上,这些数据只是一种编码,并不代表水果本身。
回到最基本的ASCII编码,为什么要有ASCII编码呢?其实,如果不用显示在屏幕上,而只是让计算机去进行数据计算,是不必使用编码的,使用正常的数制去计算即可,比如,十进制和十六进制的运算。编码,通常都是为了实现显示。
就拿ASCII编码来说,为了表示各种各样的字符,就需要给各个字符一个编码,计算机看到这种类型的编码数字时,就会去显示对应的字符。比如,字符'a',我怎么才能知道你想显示字符'a'呢?首先,你定义了一个变量,类型为char,我就知道你操作的是一个字符,当我看到你字符在底层对应的二进制编码是01100001时(对应十进制97,十六进制0x61),查找ASCII表,我就知道你要显示的是字符‘a’,我就会打点显示了。
用条件语句来判断,逻辑就是:如果类型是字符,并且编码是97,那就表示字符‘a’;
用条件语句来判断,逻辑就是:如果类型是水果,并且编码是1,那就表示苹果;
计算机是以二机制形式来存储的,他只认识0和1两个数字。我们在屏幕上看到的文字在存储前已经被转换成了二进制,在显示时也要根据二进制找到对应的字符。
所以,特定的字符必然对应着固定的二进制,否则在转换时会造成混乱。那么,怎样将文字与二进制对应起来呢?这就需要一套规范,计算机公司和软件开发公司都要遵守,这样的一套规范就被称为字符集(Character Set) 或 字符编码(Character Encoding)。
严格来说,字符集和字符编码不是一个概念,字符集定义了文字与二进制的对应关系,为字符分配了唯一编号。而字符编码规定了如何将文字的编号存在计算机中。我们先暂时认为他们是同一个东西,后文会做详细介绍。
字符集为每个字符分配一个唯一的编号,类似于学生的学号。通过这些编号就能找到对应的字符。
可以将字符集理解为一个很大表格,里面列出了所有字符和二进制的对应关系。计算机显示文字或者存储文字,就是一个查表的过程。
在计算机发展的过程中,先后出现了几十种甚至上百种的字符集,有些还在使用,有些已经淹没在历史的长河中。接下来会介绍几种现在还在使用的字符集以及了解一些现在未使用的一些字符集。
详细内容参考:
C语言学习(四)ASCII、GB2312、GBK等编码以及Unicode等字符集_unicode字符集,将全世界的文字存储到计算机_JayerZhou的博客-CSDN博客
以下仅记录关键内容。
ASCII编码
在 ASCII 编码中,大写字母、小写字母和阿拉伯数字都是连续分布的,这给程序设计带来了很大的方便。例如要判断一个字符是否是大写字母,就可以判断该字符的 ASCII 编码值是否在 65~90 的范围内。
ASCII中回车的编码如何理解?当识别到是回车对应的编码时,显示时换行。
稍微有点C语言基础的会认为C语言用的就是ASCII编码,字符在存储时会转换成对应的ASCII码值,在读取时也是根据ASCII码找到对应字符,这句话时错误的。C语言有时候使用ASCII码有时候不是,他有时候会使用接下来我们要说的GBK 编码和Unicode字符集。
GB2312编码和GBK编码,将中文存入计算机
计算机是一种轰动全国的发明,很快就从美国传到了世界各地,并得到广泛的认可,成了一种不可替代的工具。在计算机广泛流行的过程中遇到的一个棘手的问题就是字符编码。计算机是美国人发明的,他们用的是ASCII编码,只能显示英文字符,对汉语、韩语、日语、德语等其他国家的字符无能为力。
为了让本国公民也使用上计算机,各个国家开始也开始效仿ASCII,开发属于他们自己的字符编码。这些编码和ASCII一样,只考虑本国的语言文化,不兼容其他国家的文字。这样做的后果就是一台计算机上必须安装多个字符编码,否则就不能正确的跨国传输数据。例如,在中国编写的文本文件,在日本的电脑上就无法打开,或者打开就是一堆乱码。
下面列出了常见的字符编码:GB18030 |共收录 70244 个汉字,支持中国国内少数民族的文字,以及日语韩语中的汉字。
由于ASCII先入为主,已经使用了十来年,现有很多软件和文档都是基于ASCII的,所以后来的这些字符编码都是在ASCII基础上进行扩展的,他们都兼容ASCII,以支持这些软件和文档。兼容ASCII的含义是,原来ASCII中已经包含的字符,在国家编码(地区编码)中的位置不变(也就是编码值不变),只是在这些字符的后面增添了新的字符。
如何存储?
标准 ASCII 编码共包含了 128 个字符,用一个字节就足以存储(实际上是用一个字节中较低的 7 位来存储),而日文、中文、韩文等包含的字符非常多,有成千上万个,一个字节肯定是不够的(一个字节最多存储 28 = 256 个字符),所以要进行扩展,用两个、三个甚至四个字节来表示。
在制定字符编码时还要考虑内存利用率的问题。我们经常使用的字符,其编码值一般都比较小,例如字母和数字都是 ASCII 编码,其编码值不会超过 127,用一个字节存储足以,如果硬要用多个字节存储,就会浪费很多内存空间。
为了达到「既能存储本国字符,又能节省内存」的目的,Shift-Jis、Big5、GB2312 等都采用变长的编码方式:
对于原来的 ASCII 编码部分,用一个字节存储足以;
对于本国的常用字符(例如汉字、标点符号等),一般用两个字节存储;
对于偏远地区,或者极少使用的字符(例如藏文、蒙古文等),才使用三个甚至四个字节存储。
总体来说,越常用的字符占用的内存越少,越罕见的字符占用的内存越多。
中文编码方案
GB2312 --> GBK --> GB18030 是中文编码的三套方案,出现的时间从早到晚,收录的字符数目依次增加,并且向下兼容。GB2312 和 GBK 收录的字符数目较少,用 1~2个字节存储;GB18030 收录的字符最多,用1、2、4 个字节存储。
从整体上讲,GB2312 和 GBK 的编码方式一致,具体为:
对于 ASCII 字符,使用一个字节存储,并且该字节的最高位是 0,这和 ASCII 编码是一致- 的,所以说 GB2312 完全兼容 ASCII。
对于中国的字符,使用两个字节存储,并且规定每个字节的最高位都是 1。例如对于字母A,它在内存中存储为 01000001;对于汉字“中”,它在内存中存储为 11010110 11010000。由于单字节和双字节的最高位不一样,所以字符处理软件很容易区分一个字符到底用了几个字节。
字符处理软件在处理文本时,从左往右依次扫描每个字节:
- 如果遇到的字节的最高位是 0,那么就会断定该字符只占用了一个字节;
- 如果遇到的字节的最高位是 1,那么该字符就占用了两个字节。
牛掰的GBK编码
GBK 于 1995 年发布,这一年也是互联网爆发的元年,国人使用电脑越来越多,也许是 GBK 这头猪正好站在风口上,它就飞起来了,后来的中文版 Windows 都将 GBK 作为默认的中文编码方案。
注意,这里我说 GBK 是默认的中文编码方案,并没有说 Windows 默认支持 GBK。Windows 在内核层面使用的是 Unicode 字符集(严格来说是 UTF-16 编码),但是它也给用户留出了选择的余地,如果用户不希望使用 Unicode,而是希望使用中文编码方案,那么这个时候 Windows 默认使用 GBK(当然,你可以选择使用 GB2312 或者 GB18030,不过一般没有这个必要)。
实际上,中文版 Windows 下的很多程序默认使用的就是 GBK 编码,例如用记事本程序创建一个 txt 文档、在 cmd 或者控制台程序(最常见的C语言程序)中显示汉字、用 Visual Studio 创建的源文件等,使用的都是 GBK 编码。
获取任意字符串的GBK2312码
获取任意字符串的GBK2312码,需要查表,keil本身自带了GBK2312码表,如果源码使用的就是GBK2312编码方式,那么C语言的实现就比较简单:
void printgb(unsigned char* s) { while (*s) { if (*s >= 0x7f) { printf("%02x%02x ", *s, *(s+1)); s += 2; } else { printf("%04x ", *(s++)); } } } int main() { printgb("2017你好中国great china"); return 0; }
遍历字符对应的字节,如果是ASCII字符,则直接输出对应的编码值,因为源码使用的就是GBK2312编码,所以所以得到的就是GBK2312编码值,又因为GBK2312兼容ASCII,一个ASCII只占用一个字节,所以指针往后加1继续遍历;如果是汉字,那么就依次输出两个字节,因为一个汉字就是占用两个字节,然后指针往后加2继续遍历,知道遇到字符串结束字符。
由此可见,字符串在底层,也是一个字符一个字符地存储的。
那么,如果源码不是使用GBK2312编码呢?就需要包含表然后查表了,这里暂不研究。