5.2 文字和编码
在计算机中,文字信息的存储、传输和处理是相当重要的工作,而C语言里的数组又是容纳文字信息的最佳容器。
不管是学生成绩,还是代码、声音、图像和文字,在计算机内部看起来都一样,都表现为无差别的数字,要看你怎么去解释和处理它们。不过话又说回来了,本质上不同的东西,即便都是数字,它们也具有不同的规律和内在逻辑。比如说,你在键盘上敲出的文章是一大堆数字,数码相机生成的也是一大堆数字,虽然用二进制编辑器打开之后看起来没有什么区别,但你无法用图片浏览器来打开你的文章,因为这根本就不是合法的图像文件;你也无法用文本编辑器打开你的照片,因为它本来就不是文本。
这就是说,如果你用数组来保存一段文字,那么,尽管你保存的实际上是一长串数字,但它们实际上是字符在计算机内部的编码(代码)。
在不同的设备之间传输文本信息,传输的实际上是每个字符的代码。这就涉及一个非常关键的工作:必须制定一个所有设备都认可的字符编码标准,文本的发送方和接收方都知道每个数字代表的是什么字符。否则的话,当你的朋友用手机给你发送一条短信时,你的手机将无法识别,也不能正确显示这些字符。
要做到这一点,首先必须建立一个字符集,或者说字符表。世界上的语言文字那么多,所以这并不是一件轻而易举的工作。然而在计算机发展的早期,没有人会考虑那么长远,因为没有人会想到计算机会流行到每个人都有好几个,像桌面计算机、智能手表、手机、平板电脑这些都是计算机。
在那个时代,包括C语言诞生的时候,显示器还不是标准配置,对计算机的控制往往通过电传打字机进行。电传打字机在当时是比较先进的设备了,它是打字机、打印机、卡片阅读机和纸带穿孔机的集合体。电传打字机的打印机可以在纸上打印字符,相当于现在的显示器;打字机呢,相当于现在的键盘。电传打字机可以把输入,也就是人类通过打字机进行的操作传送到计算机,而打印机则可以把计算机的响应打印在纸上。
当时还没有个人计算机,有的只是非常昂贵的大家伙,称为主机。当时也已经有了多用户的操作系统,也就是允许很多人通过电传打字机来共享同一台电脑主机的计算能力。在这种情况下,每一台电传打字机就是一个终端。有些终端离主机很近,有些则很远,需要通过电话线和调制解调器来与主机连接。
在一部叫Apartment的外国黑白电影中,我们的主人公C.C.巴克斯特就职于联合保险公司的普通保单清算部,差不多有百十多号人,每人面前都有一个电传打字机(如图5-3所示),它们都连接到据说是IBM公司的大型主机上。
图5-3 电传打字机
普通的终端对主机的操作能力有限,所以每个主机通常还有一个身份特殊的终端,它是主机的一部分,用于直接对主机进行控制,叫作控制台。比如说,重启主机就只能在控制台上进行,对主机的调整也只通过控制台进行。
这样的计算机系统在现在看来是相当粗笨的,但那个时候却很先进。在这种计算机系统上,诞生了我们现在学习的C语言,也产生了UNIX操作系统。所以直到现在,C语言和UNIX系统都还保留着那个时代的很多印记。
由于这种在现在看来极不直观的操作环境,所有的设计都只能注重实用,能少打字就少打字,以简单为美,所以C语言和UNIX系统的语法及命令都非常简洁。例如,输入一个命令之后如果在执行的过程中没有错误,则系统不会显示任何消息,即所谓的“没有消息就是最好的消息”,这很可能是为了节约纸张。
再比如,尽管现在我们用的是显示器和键盘,但它们依然保留了那个时代的很多做法和叫法。我们现在把基于命令行的显示器称为控制台;在屏幕上输出字符称为“打印”;光标移到下一行称为“换行”,移到行首称为“回车”,等等,这都是那个时代才有的叫法。
有些设备可以和主机交换任何数据,例如一个外部的存储设备。这样的设备并不关心主机传来的是什么,也不试图去理解或者解释数据的含义,它只负责将数据写在磁盘或者磁带上,或者把它们读出来传送给主机。对于这样的设备和数据传输,我们称之为“纯二进制模式”的通信。
但是,像电传打字机这样的设备就不同了,发明它们的目的就是为了处理文字符号,它们必须解释主机发来的内容(文字编码)并在纸上打印出形状来。而且,用户在键盘上敲击一下,那是一个字符,要编码之后发送到主机。对于这样的设备和数据传输,我们称之为“文本模式”的通信。
终端和主机之间要想正常通信,必须有一个双方都能识别的字符编码方案,这并不是什么困难的事情。计算机诞生在美国,美国人心想,我们只有26个大写字母和26个小写字母,以及10个阿拉伯数字,外加一些标点符号和用来控制设备通信的代码,这很容易。所以一些大的计算机厂商各自设计了一些字符集和编码方案,比如EBCDIC字符集和ASCII字符集,等等,让来自不同厂家的设备能够互联。其中,1967年由美国国家标准协会牵头设计出的美国信息交换标准代码(简称ASCII)最为流行,并一直延续到现在。
创建了字符集,事情只能算是完成了一半,因为还没有为每个字符分配代码。每个被选入字符集的字符,在整个字符集中的位置是固定的,类似于每个字在字典中都占有一个固定的位置。从第一个字符开始,每个字符都有一个序号,这叫作代码点或者代码位置。
然而,代码点并不是字符编码,它仅仅是一个数学意义上的数字,指示字符在表中的位置。而字符编码(代码)呢,通常是由代码点转换而来,但是考虑到现实的需求和软硬件的限制,可能会有不同的编码方案。
对于像ASCII这样很小的字符集来说,字符的编码工作十分简单。美国人的做法是直接将代码点当字符代码来用。比如说,字符“A”是ASCII中的第65个字符,所以该字符的编码是65。由于ASCII只有128个字符,所以只需要7个比特就能编码所有字符。现代的计算机每个字节至少有8个比特,在存储ASCII字符时,第8个比特置0,这个特点深远地影响了其他国家和地区的字符编码方式。
要使用C语言编程,你的计算机必须能够提供它所要求的字符集。在C语言里有关键字和各种各样的运算符,它们都是字符或者由字符组成,比如赋值运算符“=”和关键字“sizeof”。如果一台计算机所使用的字符集里没有这些字符,那你就无法在这台计算机上用C语言编程。
所以,尽管C语言对使用何种字符集不做限定,对字符如何编码也不关心,但最基本的要求还是有的。具体地说,只有包含以下字符的字符集才能够被C语言所接受:
26个大写英文字母:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
26个小写英文字母:
a b c d e f g h i j k l m n o p q r s t u v w x y z
10个十进制数字字符:
0 1 2 3 4 5 6 7 8 9
29个图形字符:
! " # % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ _ { | } ~
以及空格、水平制表符、垂直制表符、换页符(传统上,这些字符用于控制显示设备或者电传打字机的字符定位)。
绝大多数计算机系统所使用的字符集虽然不是ASCII,但与它兼容,上述字符的编码也一样,所以不必担心。