C 语言程序设计:模块

目录汇总:C 语言零基础入门教程

设计 C 程序(或其他任何语言的程序)时,最好将它看作一些独立的模块。模块是一组服务的集合,其中一些服务可以被程序的其他部分(称为客户)使用。每个模块都有一个接口来描述所提供的服务。模块的细节(包括这些服务自身的源代码)都包含在模块的实现中。

在 C 语言环境下,这些“服务”就是函数。模块的接口就是头文件,头文件中包含那些可以被程序中其他文件调用的函数原型。模块的实现就是包含该模块中函数定义的源文件。

为了解释这个术语,我们来看一下 C 语言零基础入门教程 的第 15 部分中的计算器程序。这个程序由 calc.c 文件和一个栈模块组成。calc.c 文件包含 main 函数,栈模块则存储在 stack.h 和 stack.c 文件中(见下面的图)。文件 calc.c 是栈模块的客户;文件 stack.h 是栈模块的接口,它提供了客户需要了解的全部信息;stack.c 文件是栈模块的实现,其中包括栈函数的定义以及组成栈的变量的声明。

C 语言程序设计

C 库本身就是一些模块的集合。库中每个头都是一个模块的接口。例如,<stdio.h> 是包含输入/输出函数的模块的接口,<string.h> 是包含字符串处理函数的模块的接口。

将程序分割成模块有一系列好处。

  • 抽象。如果模块设计合理,则可以将其作为抽象对待。我们知道模块会做什么,但不需要知道这些功能的实现细节。因为抽象的存在,所以不必为了修改部分程序而了解整个程序是如何工作的。同时,抽象让一个团队的多个程序员共同开发一个程序更容易。一旦对模块的接口达成一致,实现每一个模块的责任可以被分派到各个成员身上。团队成员可以更大程度上相互独立地工作。
  • 可复用性。任何一个提供服务的模块都可能在其他程序中复用。例如,我们的栈模块就是可复用的。由于通常很难预测模块的未来使用情况,最好将模块设计成可复用的。
  • 可维护性。将程序模块化后,程序中的错误通常只会影响一个模块实现,因而更容易找到并修正错误。在修正了错误之后,重建程序只需重新编译该模块实现(然后重新链接整个程序)即可。更广泛地说,为了提高性能或将程序移植到另一个平台上,我们甚至可以替换整个模块的实现。

上面这些好处都很重要,但其中可维护性是最重要的。现实中大多数程序会使用许多年,在使用过程中会发现问题,并做一些改进以适应需求的变化。将程序按模块来设计会使维护更容易。维护一个程序就像维护一辆汽车一样,修理轮胎应该不需要同时检修引擎。

我们就以 第 16 部分第 17 部分 中的 inventory 程序为例。最初的程序(参见 C 语言嵌套的数组和结构)将零件记录存储在一个数组中。假设在程序使用了一段时间后,客户不同意对可以存储的零件数量设置固定的上限。为了满足客户的需求,我们可能会改用链表(C 语言链表简介 中就是这么做的)。为了做这个修改,需要仔细检查整个程序,找出所有依赖于零件存储方式的地方。如果一开始就采用不同的方式来设计程序(使用一个独立的模块来处理零件的存储),就可能只需要重写这一个模块的实现,而不需要重写整个程序。

一旦确定要进行模块化设计,设计程序的过程就变成了确定究竟应该定义哪些模块,每个模块应该提供哪些服务,以及各个模块之间的相互关系是什么。现在就来简要地看看这些问题。如果需要了解程序设计的更多信息,可以参考软件工程方面的图书,比如 Ghezzi、Jazayeri 和 Mandrioli 的 Fundamentals of Software Engineering。

一、内聚性与耦合性

好的模块接口并不是声明的随意集合。在设计良好的程序中,模块应该具有下面两个性质。

  • 高内聚性。模块中的元素应该彼此紧密相关。可以认为它们是为了同一目标而相互合作的。高内聚性会使模块更易于使用,同时使程序更容易理解。
  • 低耦合性。模块之间应该尽可能相互独立。低耦合性可以使程序更便于修改,并方便以后复用模块。

我们的计算器程序有这些性质吗?实现栈的模块明显是具有内聚性的:其中的函数表示与栈相关的操作。整个程序的耦合性也很低,文件 calc.c 依赖于 stack.h(当然 stack.c 也依赖于 stack.h),但除此之外就没有其他明显的依赖关系了。

二、模块的类型

由于需要具备高内聚性、低耦合性,模块通常分为下面几类。

  • 数据池。数据池是一些相关的变量或常量的集合。在 C 语言中,这类模块通常只是一个头文件。从程序设计的角度来说,通常不建议将变量放在头文件中,但建议把相关常量放在头文件中。在 C 库中,<float.h> 头和 <limits.h> 头都属于数据池。
  • 。库是一个相关函数的集合。例如 <string.h> 头就是字符串处理函数库的接口。
  • 抽象对象。抽象对象是指对于隐藏的数据结构进行操作的函数的集合。(这个部分中的术语“对象”含义与其他部分中的不同。在 C 语言术语中,对象仅仅是可以存储值的一块内存,而在这个部分中,对象是一组数据以及针对这些数据的操作的集合。如果数据是隐藏起来的,那么这个对象是“抽象”的。)我们讨论的栈模块属于这一类。
  • 抽象数据类型(ADT)。将具体数据实现方式隐藏起来的数据类型叫作抽象数据类型。客户模块可以使用该类型来声明变量,但不会知道这些变量的具体数据结构。如果客户模块需要对这种变量进行操作,则必须调用抽象数据类型模块所提供的函数。抽象数据类型在现代程序设计中起着非常重要的作用。我们会在 C 语言程序设计:抽象数据类型C 语言程序设计:栈抽象数据类型C 语言程序设计:抽象数据类型的设计问题 中回过头来讨论。

请参阅

(完)

comments powered by Disqus