程序(Program)是一个精确说明如何进行计算的指令序列。这里的计算可以是一些数学上的计算,比如解方程或者求多项式的根,也可以是符号运算,一个简单的例子是查找和替换文档中的词,一个复杂的例子是搜索引擎。从根本上说,计算机是由数字电路组成的运算机器,只能对数字做运算,程序之所以能做符号运算是因为符号在计算机内部也是用数字来表示的。此外,程序还可以处理声音和图像,同样因为声音和图像在计算机内部是用数字来表示的,这些数字再通过专门的硬件设备转换成人可以听到、看到的声音和图像。
程序由一系列指令(Instruction)组成,指令是指示计算机做某种运算的命令,通常包括以下几类:
对于程序来说,有上面这几类指令就足够了。你曾用过的任何一个程序,不管它有多么复杂,都是由上面这几类指令组成的。程序是那么的复杂,而编写程序可以用的指令却只有这么简单的几种,这中间巨大的落差就要由程序员去填了,所以编写程序理应是一件相当复杂的工作。编写程序可以说就是这样一个过程:把复杂的任务分解成子任务,把子任务再分解成更简单的任务,层层分解,直到最后简单得可以用以上指令来完成。
在不同的编程语言(Programming Language)中,以上几种指令具有不同的形式。通常“指令”这个词专指机器语言(Machine Language)或者汇编语言(Assembly Language)等低级语言(Low-level Language)中的指令,而在C语言、C++、Java、Python等高级语言(High-level Language)中通常称为语句(Statement)或表达式(Expression)[1]。举个例子,同样一个语句用C语言、汇编语言和机器语言表示如下:
表 1.1. 同一个语句的三种表示
C语言 | a=b+1; |
汇编语言 | mov -0xc(%ebp),%eax |
机器语言 | 8b 45 f4 |
计算机只能对数字做运算,虽然高级语言中有大量的符号,但这些符号都是人为定义的,最终转换成计算机可以直接处理的机器语言仍然是数字,上表中的机器语言完全由十六进制数字组成。最早的程序员都是直接用机器语言编程,但是很麻烦,需要查大量的表格来确定每个数字表示什么意思,编写出来的程序很不直观,很容易出错,于是有了汇编语言,把机器语言中的一组一组数字用助记符(Mnemonic)来表示,直接用这些助记符写出汇编程序,然后让汇编器(Assembler)去查表把助记符替换成数字,也就把汇编语言翻译成了机器语言。从上面的例子可以看出,汇编语言和机器语言的指令是一一对应的,汇编语言有三条指令机器语言也有三条指令,汇编器就是做一个简单的替换工作,例如在第一条指令中,把movl ?(%ebp),%eax
这种格式的指令替换成机器码8b 45
,把指令中的-0xc
替换成机器码f4
(这是补码表示)。
从上面的例子还可以看出,C语言的语句和低级语言的指令之间不是简单的一一对应关系,一条a=b+1
语句要翻译成三条汇编或机器指令,这个过程称为编译(Compile),由编译器(Compiler)来完成,显然编译器的功能比汇编器要复杂得多。用C语言编写的程序必须经过编译转成机器指令才能被计算机执行,运行编译器程序要消耗一些时间,这是一个小小的缺点,而优点则是不可胜数的。首先,用C语言编程更容易,写出来的代码更紧凑,可读性更强,也更容易改正。其次,C语言是可移植的(Portable)或者称为平台无关的(Platform Independent),平台这个词有很多种解释,可以指计算机体系结构(Architecture),也可以指操作系统(Operating System),也可以指两者的组合。不同的计算机体系结构有不同的指令集(Instruction Set),可以识别的机器指令格式是不同的,直接用某种计算机的汇编或机器指令写出来的程序只能在这种计算机上执行,然而各种计算机上都有C编译器,可以把C程序编译成该计算机自己的(Native)机器指令,这意味着用C语言写出来的程序只需要稍加修改甚至不用修改就可以在不同的计算机上编译执行。各种高级语言都具有C语言的这些优点,所以绝大部分程序是用高级语言编写的,只有和硬件关系密切的少数程序(例如驱动程序)才会用到低级语言。
总结一下编译执行的过程,首先你用文本编辑器写一个C程序,然后保存成一个文件,例如program.c
(通常C程序的文件名后缀是.c),这称为源代码(Source Code),然后运行编译器对它进行编译,编译的过程并不执行程序,而是把源代码全部翻译成机器指令,再加上一些描述信息,生成一个新的文件,例如a.out
,这称为目标代码(Object Code)或可执行代码(Executable)[2],这个可执行代码才是计算机可以执行的程序。如下图所示:
有些高级语言以解释(Interpret)的方式执行,解释执行的过程和C语言的编译执行过程很不一样,例如写一个Python源代码,保存成program.py
(通常Python程序的文件名后缀是.py),然后,并不需要生成目标代码,而是直接运行解释器(Interpreter)执行该源代码,解释器是一行一行地翻译源代码,边翻译边执行的。如下图所示:
编程语言仍在发展演化。以上介绍的机器语言称为第一代语言(1GL,1st Generation Programming Language),汇编语言称为第二代语言(2GL,2nd Generation Programming Language),C、C++、Java、Python等可以称为第三代语言(3GL,3rd Generation Programming Language)。目前已经有了4GL(4th Generation Programming Language)和5GL(5th Generation Programming Language)的概念,主要区别在于,4GL以后的语言主要不是通过输入、输出、基本运算、测试分支和循环这些基本指令来编程的,4GL以后的语言更多是在描述要做什么(Declarative)而不是描述具体一步一步怎么做(Imperative),具体一步一步怎么做完全交由编译器或解释器决定,例如SQL语言(SQL,Structured Query Language,结构化查询语言)就是这样的例子。
1、解释执行的语言相比编译执行的语言有什么优缺点?
这是我们的第一个思考题。本书的思考题通常要求读者系统地总结当前小节的知识,结合以前的知识,并经过一定的推理,然后作答。本书强调的是基本概念,读者应该抓住概念的定义和概念之间的关系来总结,比如本节介绍了很多概念:程序由语句或指令组成,在高级语言写的程序中通常叫语句,在低级语言写的程序中通常叫指令,计算机只能执行低级语言中的指令,高级语言要执行就必须先翻译成低级语言,翻译的方法有两种--编译和解释,虽然有这样的不便,但高级语言有一个好处是平台无关性。什么是平台?一种平台,就是一种体系结构,就是一种指令集,就是一种机器语言,这些都可看作是一一对应的,上文没有明确讲它们之间是一一对应的但读者应该能推理出这个结论,而高级语言和它们不是一一对应的,因此高级语言是平台无关的,概念之间像这样的数量对应关系尤其重要。那么编译和解释的过程有哪些不同?主要的不同在于什么时候翻译和什么时候执行。
现在回答这个思考题,根据编译和解释的不同原理,能否在执行效率和平台无关性等方面做一下比较?
希望读者掌握以概念为中心的阅读思考习惯,每读一节就总结一套概念之间的关系图画在书上空白处。如果读到后面某一节看到一个讲过的概念,但是记不清在哪一节讲过了,没关系,书后的索引可以帮你找到它是在哪一节定义的。