续接图解软件:3.软件与编程语言(上)
4.程序的源代码
我们下面来专门说说计算机程序的源代码。只要我们编写的一些内容完全符合某个编程语言的语法规则和书写规范,那么就可以说,这些内容即是基于这种编程语言的程序源代码。而且,就像我们平常向计算机中输入的数字、文字和符号那样,基于某种编程语言的源代码也是一种文本(text)。
解释一下,所谓文本,就是由一些字符组成的内容。这里的重点是,这些字符都是我们可以识别的,或者说对于我们来讲有明确意义的。比如,Unicode编码规范支持的那些字符。即使其中出现了一些不可见的控制类字符,我们也是可以感知到的,如换行符、回车符、制表符等等。因此可以说,文本是直观的,是“所写即所见,所见即所得”的。
图3-12一段形如表格的文本
相对应的,由一些与字符没有直接对应关系的二进制数字组成的内容就不是文本。这样的内容对于我们来说没有明确的意义。然而,它们却可能是二进制形式的CPU指令和数据,可以被计算机识别和处理。如果我们在计算机上打开只包含了这种内容的文件(也称二进制文件,如含有可执行程序的文件),那么看到的只可能是一堆“乱码”。这意味着,任何针对字符的编码方案都无法对它们进行有效的解码。即使编码方案碰巧把其中的一些二进制数字解码成字符,在我们看来往往也是混乱的、没有意义的。
图3-13一段二进制文件的内容
另一方面,源代码与其他的文本相比存在一个很大的不同。我们在书写段落或文章的时候可以掺杂多种自然语言的文字,比如在一个段落中既包含中文字又包含英文单词。然而,这对于源代码来说几乎是行不通的。
我们通常会把程序的源代码存储在一个或多个文件当中。这样的文件也被称为源码文件,是一种程序单位。在绝大多数情况下,对于任何一个源码文件,其中的源代码都必须只基于某一个编程语言。否则,内容混杂的源码文件是无法被任何一个程序构建工具正确地识别和处理的。
图3-14源代码的排他性
对于大多数编程语言来说,它们之间都有一堵无形的墙,使得基于它们的源代码无法在同一个“单元”内混合。当然了,这里也存在一些变通的情况。不过,那些情况必须严格地满足一些先决条件否则就不可能成行,而且实现起来也比较复杂。因此,我们就不在这里赘述了。
5.源代码的组织
在我们正式编写计算机程序的时候,一定会把源代码存放到一个或多个源码文件当中。这些源码文件可能都处在同一个目录,也可能处于不同的目录。通常来说,这种目录就可以充当程序包。只不过,一些编程语言可能还会对这样的目录有一些特别的要求,比如:目录的名称只能包含某些字符、目录中需要包含一两个描述性的文件等等。
在正式的程序中,一个源码文件肯定会存在于某个程序包当中。反过来讲,程序包是若干源码文件经过有效组织之后形成的聚合体。虽然这种聚合体的内部是分离和松散的(可以包含多个相对独立的文件和目录),但在其外界的程序看来它却是一种完整自洽的程序单位。程序包的形成一方面是为了对外隐藏一些程序的细节,以避免外界的程序窥探其内部或者受到其内部变化的影响。另一方面,程序包大都会统一对外提供一些API(ApplicationProgrammingInterface,即应用程序接口),或者说暴露一些程序实体以供外界的程序使用。程序实体指的是,程序员在源码文件中声明的(或者说定义的)变量、常量、函数、数据结构、类型等。它们是比程序包和源码文件更小的程序单位。
图3-15程序单位
与此同时,一个源码文件可以引用其他的程序包。虽然在各个编程语言中这种引用的具体方式不尽相同,但一般来说都是比较便捷的。通常只用一行代码就可以实现对另一个程序包的引用。甚至,我们可以把其他人编写的、不在本地(即当前计算机)的程序包从网络上下载下来并安装到指定的目录中,然后再在我们的源码文件里引用它。很多编程语言的程序包管理器都有这种下载和安装的功能。
那么,引用其他的程序包做什么呢?显然是为了使用它们的API,从而利用其能力实现我们的程序的功能。或者说,把其他人编写的程序纳入到我们的程序之中,并为我们的程序所用。
你可以把程序的编写想象成闯关游戏。如果你不会闯前几关或者嫌麻烦不想从头闯关,那么可以去寻找别人的闯关答案。若恰好有人闯过那几关并且已经把闯关答案公开了,那你就可以直接拿过来并利用它闯过前几关,然后再在这个基础上继续闯后面的关。同时,你也可以把你的闯关答案奉献出来供大家使用。这里说的闯关答案就相当于程序包。
图3-16编程如闯关
就像闯关游戏一样,我们在编写程序的过程中很可能会遇到一些疑难问题。我们可以选择自己解决或者寻找他人的解决方案。对于闯关游戏,通关的奖励可能各有不同。但对于编程来说,通关的奖励至少是,计算机接受你的指挥并忠诚地为你做事。你也会因此获得满满的成就感。这也正是编程的魅力所在。
6.软件的层次
好了,说了这么多细节上的东西,我们最后来看一看计算机软件在整个计算机系统中的位置。
图3-17各种软件在计算机中的位置
如上图所示,在计算机系统中,像CPU、内存、硬盘这类的硬件是处于最低层的。它们是整个系统的基础。CPU厂商在CPU中内嵌了ISA(即指令集架构)等附件,以便让使用者可以通过机器代码来操控计算机的硬件。由于二进制代码编写起来极其的低效,所以我们一般会用汇编语言来编写这种低级的程序,而不用机器语言。
我们在前面提到过很多次的操作系统也是一种软件,一种作为计算机基础设施的重要软件,或简称基础软件。我们平常使用的计算机和大多数的智能设备中一定都会有这种基础软件在时刻地运行着。正因为如此,它与普通的软件或程序所处的层次是不同的。
图3-18操作系统的作用
当今主流的操作系统基本上都是由C/C++语言以及汇编语言编写而成的。所以,我把C/C++语言放在了基础软件层的下面。不过不要忘了,C/C++语言属于高级的编程语言,而汇编语言和机器语言都属于低级的编程语言。而且,C/C++语言也可以被用来编写一些应用程序。
在操作系统之上,我们可以选择很多更高级的编程语言来编写应用程序。虽然C/C++语言也完全可以胜任一些应用程序的开发,但是使用它们编写出来的程序往往只有一定的跨平台能力。我们在前面也提到过,C/C++程序若要在不同种类的操作系统上运行,往往都需要经过一番修改。因为各类操作系统为应用程序提供的API都多多少少存在一些差别。
前图中的服务端程序指的是,通过某种途径(如计算机端口、互联网等)为其他程序提供服务的应用程序。而客户端程序一般是指那些专门用于访问其他程序提供的服务的应用程序。我们平常上网使用的网络浏览器也可以被看作是一种客户端程序。服务端程序和客户端程序是成对的概念。
而单机程序是,在运行的过程中几乎不与本地之外的其他程序联络的应用程序。比如,我们平常使用的计算器程序、日历程序、绘图软件,有时候玩的单机游戏,以及程序员们使用的代码编辑器,都属于单机程序。它们基本上只有在自我更新或管理程序插件的时候才会去联络远程的其他程序。
从另一个维度讲,一些应用程序针对不同的计算机有不同的“端”。比如,不少脍炙人口的软件都有桌面电脑端(或者说PC端)、平板电脑端、智能手机端、智能手表端,甚至智能电视端。不仅如此,它们的开发团队为了让软件覆盖更多的用户通常还会根据不同的操作系统发布不同的“端”,如Windows端、macOS端、iOS端、Android端等。同一个应用程序的这些“端”在界面和功能上往往会保持高度的一致,并附带协调一致的使用指南和操作助手。这使得众多“端”共同组成了一个有机的整体。顺便说一句,只有这种对用户友好且文档完善的程序才有资格被称为软件。
至于程序插件、小程序等,属于建立在平台级的应用程序(或简称平台程序)之上的轻应用程序(或简称轻应用)。轻应用体现出了一种更高级的跨平台能力。操作系统的存在使得应用程序可以不