(十六)记录 -- 6. 基于记录的应用程序设计

6. 基于记录的应用程序设计

6.1 使用数据库的重要性

目标是开发一个应用程序,提供程序化教学,使不具备编程技能的老师可以提出问题、接受答案并根据前后对照的信息使程序按照适当的顺序进行。

为了达到这个目标,可以将程序设计为一个通用的工具,该工具可以从文件中读入与程序化教学相关的信息和数据。

只要提供不同的数据文件,同一个程序就可以支持不同的课程了。


6.2 问题的框架

当程序运行时,基本的操作是重复执行以下几步操作:

(1) 提出当前的问题。一个问题可以由一行或多行文本组成,这些文本可以用字符串表示。

(2) 从学生处获取答案。答案也可以由字符串表示。

(3)查看为该问题准备的一系列答案。
如果学生的答案在上述答案中列出,则参考数据结构来选择新的问题。
如果学生的答案与提供的答案不符,则告知学生该结果并提供另一次机会回答该问题。


为使该应用程序拥有较好的可移植性,关于某门课程的所有信息必须存入一个数据文件中,而不是直接将它们写入程序。

程序的任务是读取数据文件,在内部数据结构中存储信息,然后用本节开始所描述的方法处理该结构。

因此,下一个任务是设计一个适当的数据结构,这样就可以为整个程序的编写提供一个背景。


设计数据结构的过程包括两个步骤。

首先,需要设计一个供程序使用的内部结构。内部数据结构由类型定义组成,其中结合了数组和记录,因而可以反映真实世界中信息的组成方式。

其次,需要设计能够反映数据文件中信息如何存储的外部结构。

这两个步骤是紧密关联的,主要是因为它们表示同样的信息。但是这两种结构是为了不同目的而设计的。

内部结构应便于程序员使用,而外部结构需要为课程设计者服务,不会在程序操作方面遇到太多困难。


6.3 设计内部表示

第一步需要设计一个包含了必需信息的数据结构。

数据库的设计中有一个重要概念—封装(encapsulation),这一步将相关的信息结合起来放入结构中,并作为整体处理。

对于一个大型数据库来讲,封装的过程是有层次的,且必须在每一个层上考虑细节。

在最高层,需要将整个数据库作为一个变量考虑,它包含了所需的全部信息。

因此,下图将数据库表示为指向某结构的指针,该结构的内容留待后面考虑:

当你需要将整个数据库传递给函数或过程时,你只需传递变量db即可,这是一个易于操作的指针,利用它可以访问其他数据。

只有当函数或过程需要对数据库中个别字段进行操作时,才需要查看结构中的细节。

整个数据库应该包含一系列的问题,以及其他的一些信息,如课程名称等。

这些问题应该是一个数组,但其中数组元素的类型仍未定义。因此,在目前层次上的结构分解应该如下图所示:

已知其他字段的类型,因此可以写出一个恰当的记录定义,如:

typedef struct {
	string title;
	questionT questions[MaxQuestion+1];
} *courseDB;

常量MaxQuestions为程序所允许的问题数目的最大值。


courseDB的定义使我们可以声明变量db来存放整个数据库,定义如下:

courseDB db;

但是这个声明仅为指针保留存储空间,因此是与前面所示的虚线框部分相应的。

当在程序中创建结构时,需要为该结构分配空间,代码如下:

db=New(courseDB);

关于questionT的定义,问题由文本组成,其中包括多行文件以及一系列可能的答案。

这两个结构都可由数组表示。

问题的文本是字符串的数组,每一个字符串包含了一行的内容。

而答案存储在一个结构较为复杂的数组中。

目前,我们可以将答案声明为一个类型answerT的数组,其细节将在下一步完善。


通常情况下,我们需要提供一个机制来追踪数组的有效大小,因为它通常比所分配的空间小一些。

记录有效大小可以利用下面两种方式:

  • 在数组的最后一个值后面增加一个标记值。
  • 将元素的个数存入一个整型变量,并将其作为记录的一部分。

究竟采用哪种方式取决于数组的使用方式,以及是否可以选择一个合适的标记值。


为了介绍这两种模式的操作,示例程序采用了两种定义questionT的方式。

组成问题文本的行存入了一个由字符串组成的数组。

对于指针数据来讲,常量NULL是一个最佳的标记值。在此例中更适合用一个变量存储答案的数量。

一个问题的结构可用下图表示:

因为这个结构比较复杂,而且需要将其考虑为一个整体,因此可以将此结构定义为一个指针类型,如下所示:

typedef struct {
	string qtext[MaxlinesPerQuestion+1];
	answerT answers[MaxAnswersPerQuestion];
	int nAnswers;
} *questionT;

将此结构声明为一个指针意味着必须调用New来为每个问题分配空间。


设计数据结构的最后一步是定义类型answerT。

一个答案通常由下面的内容组成:标准答案以及相应的下一个问题。

标准答案为一个字符串,而下一个问题可以由存放了问题编号的变量表示。

因此,答案的结构应该如下图所示:

相应的结构应该为由一个字符串和一个整型值组成的记录:

typedef struct{
	string ans;
	int nextq;
} answerT;

在这里,此结构足够小,以便可以将其定义为记录而不用定义为指针。

这样就可以用下图表示完整的数据结构了:


6.4 设计外部结构

在对数据的内部结构进行定义之后,就要决定如何在数据文件中表示相同的信息。

在此例中,最简单的方法是依次写出每个问题,以及可能的答案。

为了使计算机能够将每个问题区分开来,需要定义某些规则来区别每个问答单元。在此例中最好使用一个空行。


一个问答单元都包含哪些内容呢?

首先,包含问题的文本,文件中的若干行构成了一个问题。

我们同样也需要用某种方式表示问题结束,最简单的方法是定义一个标记值。在这个程序中,可以选定用五个“-”来表示文件结束。

另外,程序同样需要允许课程设计者指明答案/下一问题的配对。在此,我们选择如下的方式:

在一个数据行中先列出答案的文本,然后用冒号分隔,再跟随下一问题的序号。

因此,文件中的每条问题都是这样的:


如何将问题序号与每一个问题联系起来?

更好的办法是由程序编写者给每个问题编一个序号。

例如,如果示例中关于地球和太阳的问题是1号的话,它的文件可以包含该问题的编号,如下图所示:






参考

《C语言的科学和艺术》 —— 16 记录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值