5. 创建记录的数据库
用来存储结构化对象集合的数据结构(特别是当这些结构存储在一个数据文件中时)常被称为数据库(database)。
当需要通过其他函数访问数据时,同时传递两个变量(数组本身及其有效大小)会造成很大的麻烦。
如果将数组的有效大小与数据记录结合为一个单独的对象,会使操作变得简单。
可以定义一个新的结构类型来完成上述工作,该类型包含了上述的两部分值。
将新类型定义为指向记录的指针效率会更高。因此,整个员工数据库可以按照下面的结构定义:
typedef struct {
employeeT staff[MaxEmployees];
int nEmployees;
} *employeeDB;
此定义创建了一个名为employeeDB的新类型,可以用以下代码来声明employeeDB类型的变量,如下所示:
employeeDB db;
5.1 创建员工信息数据库
到目前为止,变量db仅包含了指针的内存空间,且没有为该变量分配任何存储空间。
仍然需要为记录的字段以及每位员工的记录分配存储空间。
为更好地理解如何操作,可以画出需要创建的结构。
和程序分解的过程一样,最好的方法是自顶向下画出结构图,然后再对每一个层次的结构进行精雕细琢。
在第一层中,数据库仅仅是一个指针,它指向的记录的具体内容暂时无需了解,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/159ff80c011a3377095a43c64b961647.png)
为完善上图, 需要查看employeeDB记录的细节,这时就可以用由包含数组staff及员工实际数量的结构来替代虚线框。如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/7e00d5dcef4583b8ce5c74c41bf8c133.png)
最后,staff数组中的每一个元素(根据最近的employeeT类型定义)都是一个指向记录的指针, 因此完整的图应该包含指向单独employeeT结构的指针,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/e6a7d0b3b334aa4b0550b639befcaab2.png)
为了创建这个结构,应该从部分着手。对于结构中的每一个由指针指向的方框,都需要调用函数New来为该记录分配存储空间。
假设我们需要编写程序从用户处读入一个员工信息数据库。对于每一个记录,程序需要得到每个雇员五方面的信息。
需要用一个空白的name字段作为数据库结束的标记。每个记录都应该存入数据库中,且要对记录进行计数。
读入数据库的操作,应该定义为一个函数。
由于数据库为一个指针, ReadEmployeeDatabase只能分配指针需要的存储空间,并返回一个指针。
因此,主程序声明一个数据库:
employeeDB db;
并通过调用以下函数读入值:
db = ReadEmployeeDatabase();
ReadEmployeeDatabase的实现, 采用逐步精化的方法。
只需将其分解为两个函数:
函数ReadEmployeeDatabase创建资料库结构;
函数ReadOneEmployee读入每个员工的信息。
由于函数ReadoneEmployee负责从文件中读入数据,因此该函数必须能够判断文件是否结束, 并将信息通过返回一个特殊的标志传递给ReadEmployeeDatabase。
由于函数ReadOneEmployee返回的值为employeeT类型的指针,因此最好用NULL作为文件结束标志。
函数ReadEmployeeDatabase的代码为:
static employeeDB ReadEmployeeDatabase(void) {
employeeDB db;
employeeT emp;
int nEmployees;
db = New(employeeDB);
nEmployees = 0;
printf("Enter a database name (use blank to stop).\n");
while ((emp = ReadOneEmployee()) != NULL) {
db->staff[nEmployees] = emp;
nEmployees++;
}
db->nEmployees = nEmployees;
return db;
}
函数ReadoneEmployee为每个员工分配空间,并为每个字段输入信息,代码如下:
static employeeT ReadOneEmployee(void) {
employeeT emp;
string name;
printf("Name: ");
name = malloc(50);
fgets(name, 50, stdin);
emp = New(employeeT);
emp->name = name;
return emp;
}
5.2 数据库的使用
一旦创建了一个数据库,就可以用它来作为程序的数据源,这样的程序可能用来生成报告,也可能用来计算统计结果。
例如,可以改写前面介绍的过程ListEmployees,让它运用新的数据库结构,如下所示:
static void ListEmployees(employeeDB db) {
int i;
for (i = 0; i< db->nEmployees; i++) {
printf("%s(%s)", db->staff[i]->name, db->staff[i]->title);
}
}
另外,由于employeeDB表示一个指针,同样也可以编写函数改变记录的内部数据。
下述的过程将所有与扣税款补贴大于或等于5的员工薪水增加一倍:
static void GiveRaise(employeeDB db) {
int i;
for (i = 0; i < db->nEmployees; i++) {
if (db->staff[i]->withoutholding >= 5) {
db->staff[i]->salary *= 2
}
}
}
参考
《C语言的科学和艺术》 —— 16 记录