4.1.3 编程语言对模块化编程的支持
在 1950 年代,由于计算机内存容量很小,程序员们千方百计地想尽量减小程序的大小。 汇编语言中最早出现了子例程(subroutine)和宏(macro)的构造,其目的正是为了减小程 序大小。子例程和宏可以实现了“一次编写、多处多次使用”,从而避免了在程序中的重复 代码,缩短了代码长度。
从 1960 年代开始,高级编程语言中出现了支持模块化编程的语言构造,这种构造在不 同语言中可能有不同的名称和形式,除了上面提到的子例程之外,还有子程序(subprogram)、 过程(procedure)、函数(function)以及由过程和函数组成的模块、包(package)等构造。 以下我们用“子程序”来泛指这些模块化构造。
子程序是指程序中的一段代码,它执行特定任务,并且与同一程序中的其他部分是相对 独立的。顾名思义,子程序也是程序,也是由许多计算步骤构成的指令序列;但抽象地看, 可以将一个子程序视为一个操作或高级指令,可以作为更大的程序中的一个简单步骤来使 用。在程序的一次执行中,可以多次、多处执行子程序。子程序概念虽然仍然有避免重复代 码、减小程序大小的作用,但其更重要的目的是使程序更加模块化。
子程序构造一般涉及以下一些内容:
子程序的创建:定义子程序的名字和代码(程序体)。
子程序的调用和返回:调用就是要求执行子程序,而子程序执行完毕应当将控制返 回给调用者。
参数:相当于子程序所需的输入数据,一般需要预先声明参数的类型和个数,并在 调用时提供具体的参数值。
返回值:相当于子程序的输出数据,一般需要预先声明返回值的类型。
局部变量:子程序中定义的变量在子程序外部是不可见的,亦即子程序构成了一个 私有名字空间。这是子程序独立性的一种表现。
全局变量:子程序外部定义的变量如果被声明为全局变量,那么所有子程序都可以 共享使用、操作该变量。
有的编程语言(如 Pascal 语言)同时提供两种子程序构造:过程和函数。过程不产生 返回值,因此总体上相当于一条命令语句,只规定了要执行的操作;而函数不但要执行一些 计算,更重要的是需要将计算结果返回给调用者,因此函数在使用时相当于一个数据。
有的编程语言(如 C 语言)则不将子程序区分为过程和函数,而是统称为函数。过程 就是没有返回值的函数。不过,更现代的语言(如 Python)要求函数必须具有返回值,“过 程”其实是返回某种特殊值(如 Python 中的 None)的函数。
子程序是传统的过程式语言中的模块化构造。模块化编程方法经过多年发展,又派生出 了面向对象编程方法。在面向对象语言中,对象实际上就是模块概念的推广,传统模块之间 的调用接口相应地发展成了对象之间的消息传递接口。
一个编程语言一般只提供基本的编程构造(数据类型、语句、子程序等),用户所需的 实用功能都必须由自己编程实现。为了帮助用户进行应用开发,专业软件开发商一般会为某 种编程语言开发很多提供标准功能的子程序,用这种语言编程时可以直接调用这些标准子程 序。这些标准子程序构成了所谓的程序库,它是软件重用、共享和营销的重要形式。例如 math 和 string 就是 Python 语言的标准库。同样地,面向对象编程语言则以类库的形式 为程序员提供大量的标准功能代码。
总之,子程序是强大的模块化编程工具,通过将复杂程序分解为子程序,可以大大降低 开发复杂程序的难度,使问题变得可理解、易开发。另外,子程序的独立性还意味着可以由 团队来开发复杂程序,从而提高软件生产率。最后,由于较小的子程序更容易验证正确性, 所以模块化开发还可以保证复杂程序的质量和可靠性。