跳转至

第 19 章 程序设计

将C程序看作是一组相互提供服务的模块

19.1 模块

模块是一组服务的集合。

服务是指这个文件实现了什么功能,比如这个文件定义了常量,一些方法,方法是指比如接收两个数字,返回它们相加的结果

接口

每个模块都有一个明确定义的接口,描述了其提供的服务。

C语言中,服务指函数,模块的接口指头文件

头文件包含了其它程序需要调用函数的原型。也就是说,用java的话就是抽象方法;同时,抽象方法指的是,我无需每次使用这个方法都要写一遍定义一个新的方法,而是用现有的进行调用

calc.c:程序主函数

stack.h:栈模块的接口——函数声明

stack.c:栈模块的实现——函数具体定义和变量声明

模块是由stack.h和stack.c两个文件组成的

将程序分割成模块有一系列好处 - 抽象 - 可复用性 - 可维护性

一旦我们确定要进行模块化设计,设计程序的过程就变成了确定究竟 应该定义哪些模块,每个模块应该提供哪些服务,各个模块之间的相互关系是什么。

19.1.1 内聚性与耦合性

  • 高内聚性

    模块中的元素应该彼此紧密相关。每个模块在自己领域高度专注,都是为了实现更好的功能。内聚指的是聚焦一点。

  • 低耦合性

    模块之间应该尽可能相互独立。修改一个模块不会对其它模块造成太大影响。

19.1.2 模块的类型

模块通常分为下面几类 - 数据池——一些相关的变量或常量的集合,比如头文件 - 库——一个相关函数的集合,比如,字符 串处理函数库的接口。 - 抽象对象——栈模块 - 抽象数据类型(ADT)

19.2 信息隐藏

信息隐藏优点: - 安全性——用户无需也不会知道栈是如何存储,也就不会通过栈的内部机制来修改栈的数据。它们是通过模块自身提供的函数来操作栈,而这些函数是此前编写并测试过的。 - 灵活性——换一种数据结构来表示栈,比如数组->链表,重写时只需改变模块的实现而不用修改接口。

19.3 抽象数据类型

抽象数据类型(Abstract Data Type,ADT)是一种逻辑描述,它定义了数据和对数据的操作,但并不具体说明数据应该如何存储,以及操作应该如何实现。它只是描述了数据类型的性质和特性,而不涉及具体的实现,因此被称为“抽象”。

也就是说,当你想创建1个或多个栈实例的时候——意思是你这里需要栈,另外一个地方也需要栈——那么只需要定义一个“ADT”,通过这个接口调用再来创建就好啦。

19.3.1 封装

#define STACK_SIZE 100

typedef struct 
{
    //contents 数组名称
    int contents[STACK_SIZE];
    int top;
} Stack_ADT;


void make_empty(Stack_ADT *s);
bool is_empty(const Stack_ADT *s);
bool is_full(const Stack_ADT *s);
void push(Stack_ADT *s, int i);
int pop(Stack_ADT *s);

上述代码暴露了Stack_ADT类型的具体实现方式,top和contents成员的访问,所以并不是ADT。

我们真正需要的是一种阻止客户知道Stack 类型的具体实现的方式。

封装:将抽象性函数接口的实现细节部分包装、隐藏起来的方法。也就是不把top和contents放在这个位置,找一个用户看不见的地方安置。

19.3.2 不完整类型

待了解

19.4 栈抽象数据类型

19.4.1 为栈抽象数据类型定义接口

代码已实现

19.4.3 改变栈抽象数据类型中数据项的类型