第七章 结构和模块化设计 7.1 结构
DESCRIPTION
第七章 结构和模块化设计 7.1 结构. 复合数据对象的需求 描述由不同类型的数据元素组成的数据对象 例如: 教学管理系统中的学生数据 包括:姓名、性别、学号、班级 语言支持 C 语言中的结构 Pascal 语言中的记录 C++ 、 Java 语言中的类. 语法结构 struct 结构名 { 类型 成员变量; 。。。 }; /* 学生信息的结构 * / struct Student { char name[32]; char sex; int no; char class[8]; };. - PowerPoint PPT PresentationTRANSCRIPT
第七章 结构和模块化设计7.1 结构
复合数据对象的需求 描述由不同类型的数据元素组成的数据对象
例如: 教学管理系统中的学生数据 包括:姓名、性别、学号、班级
语言支持 C 语言中的结构 Pascal 语言中的记录 C++ 、 Java 语言中的类
结构定义和变量说明 语法结构struct 结构名 { 类型 成员变量;。。。};/* 学生信息的结构 */struct Student {
char name[32];char sex;int no;char class[8];
};
结构变量说明struct Student chen;
或直接说明struct {
char name[32];char sex;int no;char class[8];
} chen;
结构变量及相关运算 结构变量占用的内存空间
大于等于各个元素占用内存空间之和 结构变量的运算
初始化如: struct Student chen= { “chen”, ‘F’, 24, “9707” }; 赋值 函数参数
结构分量的运算 引用: chen.name chen.sex 运算取决于分量的类型
例 7-1 :日期的验证#include <stdio.h>struct date {
int year; /* 年 */unsigned char month; /* 月 */unsigned char day; /* 日 */
};main( ) {
int days[ ] = {0,31,28,31,30,31,30,31,31,30,31,30,31};struct date x;do {
scanf( “%d%d%d”, &x.year, &x.month, &x.day );} while( x.day <= 0 || x.day > days[x.month]
|| x.month <= 0 || x.month > 12 ); /* 出错要求重新输入 */
printf( “It is correct date.\n” );}
程序读解 结构的使用
分量使用:各种运算、取地址、作为参数; 分量类型:指针、其他结构(不允许递归定义)
unsigned char 无符号字符( 1个字节: 0-255 ) %d 指示读整数,存到该字节
数组 days 的使用 避免 switch 语句的多分支 执行速度高于 switch
例 7-2 :洗牌和发牌模拟 任务:
考虑扑克牌的 4 个花色和 52 张,完成洗牌过程,通过输出完成发牌过程 基本思路
设置数据对象保存 52 张牌、 4 个花色和牌名信息 产生随机数来控制牌的交换,模拟洗牌过程 输出所有牌,分为 4 组,模拟发牌过程
算法设计 数据对象设计
52 张牌 cards[52]
4 个花色 suit[4]13 个牌号 face[13]
算法1. 初始化 52 张牌2. 洗牌过程3. 发牌过程
初始化依次设置每张牌的花色和牌号
洗牌过程依次处理每张牌 ( 第 i 张 )产生一个随机数 (0-51) j将第 j 张和第 i 张牌交换
程序结构设计 数据结构
采用 struct 描述扑克牌的信息(花色、牌号) 采用整数数组表示牌号 1, 2, 3, … 13 采用字符数组表示花色 H, D, C, S
程序结构 设置 3 个函数 fillDeck, shuffle, deal 分别负责初始化、洗牌和发牌的完成
程序实现 (1/3)#include <stdio.h>
#include <stdlib.h>#include <time.h>struct card {
int face; char suit;};main( ){
struct card deck[52];int face[ ] = {1,2,3,4,5,6,7,8,9,10,11,12,13};char suit[ ] = { ‘H’, ‘D’, ‘C’, ‘S’ };srand( time(NULL) );fillDesk( deck, face, suit );shuffle( deck );deal( deck );
}
通用函数头文件时间函数头文件
数组初始化
获得当前时间
初始化随机数函数
程序实现 (2/3)void fillDeck( struct card deck[ ], int face[ ], char
suit[ ] ){
int i; /* 初始化:顺序排列 52 张牌 */for( i=0; i<52; i++ ) {deck[ i ].face = face[ i%13 ];deck[ i ].suit = suit[ i/13 ];}
} 结构分量的引用
程序实现 (3/3)void shuffle(
struct card deck[ ] ){
int i, j; /* 洗牌过程 */struct card temp;for( i=0; i<52; i++ ) {j = rand( ) %52;temp = deck[ i ];deck[ i ] = deck [ j ];deck[ j ] = temp;
}}
void deal( struct card deck[ ] ){ int i; /* 发牌过程 */ for( i=0; i<52; i++ )
printf(“%d of %c%c”, deck[ i ].face, deck[ i ].suit, (i+1)%4? ‘\t’: ‘\n’ );
}
条件表达式 制表符随机数生成
几点程序说明 结构数组
元素是一个完整的结构 结构变量的赋值 temp = desk[ i ]
整个结构的复制(时间开销大) 数组初始化
可利用初值个数设置数组大小 随机数的产生
int rand( ) 返回随机数 void srand( unsigned seed ) 初始化随机数生成 time( ) 获得当前时间(单位:秒) 需要通用函数和时间函数头文件 stdlib.h, time.h
虚拟机的概念 抽象的计算机
接受一组指令,按照给定的指令序列执行 虚拟机概念在程序设计中的应用
使得一个程序模块,按照类似的方式工作: 以一组函数实现虚拟机的指令系统 以函数调用表示指令的执行
例如: 上例中,扑克牌数组以及相关的 3 个函数组成一个虚拟机; 3 个函数实现了该虚拟机的 3 个指令。 使用者( main 函数)仅负责向虚拟机发送指令(函数调用),不必关系其内部实现细节
抽象方法与信息隐蔽 抽象方法的运用
通过 3 个函数的设置( fillDeck, shuffle, deal ),完全隐蔽了扑克牌相关处理的内部计算逻辑; 虚拟机使用者的视点
仅了解 3 个函数的使用方法,好象它们是系统提供的标准函数 对软件工程的支持
虚拟机的使用者和实现者可以是不同的开发组 分别实现大型软件的不同组成部分 软件模块接口就是一组函数原型及其说明
7.2 模块化程序设计 软件模块
数据对象及其相关处理算法 程序模块:数据结构及其相关函数
例 7-1 中的模块 主控模块: main 函数 扑克牌模块:数组 deck 以及 3 个函数
信息隐蔽 模块的使用者无须连接数据对象的细节如: main 函数编制时,无须了解数组 deck 的详细定义、数据内容和结构组织。
例 7-3 :简易学生管理系统 任务:
分别输入学生的户籍信息和学籍信息,打印出学生基本信息表(假设学生人数 <250人) 户籍信息:姓名、身份证号码、出生年月日、住址; 学籍信息:学号、身份证号码、所属学院、专业、班级 学生基本信息表:学号、姓名、年龄、所属学院、班级;
数据对象 学生户籍数据、学生学籍数据 学生基本信息表可以直接输出(不保存)
算法设计1. 输入户籍数据
(每行输入一个学生的数据,空格分割各个项目)2. 输入学籍数据
(每行输入一个学生的数据,空格分割各个项目)3. 构造并输出学生基本信息表
(提取户籍和学籍数据,构造并输出学生信息表)
算法的逐步求精 1 、 2 步
仅涉及输入输出,忽略算法描述 3 构造学生基本信息表
3.1 依次从户籍数据中取出一个学生的信息 3.2 根据其身份证号码,找出该生的学籍信息 3.3 综合该生的户籍信息和学籍信息,构造基本信息记录,填入学生基本信息表 3.4 重复 3.1-3.3 的处理,直至处理完所有学生的数据
程序结构设计 数据对象
户籍数据、学籍数据、学生基本信息表 尽可能符合信息的原始结构、采用 struct 采用结构数组来保存数据
模块与函数的设计 围绕户籍数据,提供输入、依次提取的函数 围绕学籍数据,提供输入、查找的函数 为学生基本信息,提供构造并输出的函数 形成 3 个程序模块
程序结构
户籍数据模块InfoAddr数据接口InputAddr( … )输入数据
学籍数据模块InfoStudent学籍数据InputStudent( … )输入数据GetStudent( … )查找学生信息
基本信息表模块Output( … )基本信息表的输出
主控模块main( )
户籍处理模块的设计#include <stdio.h>#include <string.h>typedef struct { /* 户籍数据结构 */
char name[ 16 ]; /* 姓名 */long no; /* 身份证号 */struct {
int year, mon, day; /* 作为分量的结构 */} birthday; /* 生日 */char addr[ 128 ]; /* 地址 */
} InfoAddr;int InputAddr( InfoAddr info[ ] );
/* 输入数据到数组 info, 返回学生数量 */
学籍处理模块的设计typedef struct { /* 学籍数据结构 */
char student[ 20 ]; /* 学号 */long no; /* 身份证号 */char college[ 32 ]; /* 学院 */char class[ 10 ]; /* 班级 */
} InfoStudent;
int InputStudent( InfoStudent info[ ] );/* 输入数据到数组 info, 返回学生数量 */
InfoStudent *GetStudent( long no, InfoStudent info[ ],
int n );/* 获得身份证号为 no 的学生的学籍数据 , n 是学生数量 */
主控模块的设计void Output(int n, InfoAddr addr[ ], InfoStudent info[ ]);
/* 构造学生基本信息,按表格输出( n 是学生人数) */
main( ){
static InfoAddr addr[ 256 ];static InfoStudent stu[ 256 ];int num; /* 人数 */num = InputAddr( addr ); /* 输入户籍数据 */if( num != InputStudent( stu ) ) /* 输入学籍数据 */return;Output( num, addr, stu ); /* 输出基本信息表 */
}
设计说明 函数原型设计
考虑函数内部功能的实现中,需要用到的所有输入输出信息; 将输入信息作为参数;输出结果作为返回值,或通过参数返回; 确保信息处理的局部化
实现技术细节 对于复杂结构,利用结构,结构分量的结构 对于大的数据对象(结构数组),采用静态变量
学生基本信息表的构造和输出void Output(int n, InfoAddr addr[ ], InfoStudent info[ ]){
int i, age;InfoAddr *q; /* 结构指针 */InfoStudent *p;for( i=0; i<n; i++ ) {q = &addr[ i ]; /* 取户籍数据 */age = 2005 – q->birthday.year; /* 计算年龄 */p = GetStudent( q->no, info, n ); /* 取学籍数据 */printf( “%8ld %.16s %2d %.32s %.10s\n”, q->no, q->name, age, p->college, p->class );
}}
户籍信息的输入int InputAddr( InfoAddr info[ ] ){ /* 输入数据到数组 info, 返回学生数量 */
int n = 6;char buf[ 256 ];InfoAddr *p;for( p = info; n == 6; p++ ) {
gets(buf); /* 读入一行 */n = sscanf( buf, “%s%ld%d%d%d%.128s”,
p->name, &p->no, &p->birthday.year,&p->birthday.mon, &p->birthday.day,p->addr ); /* 读入到数组元素中 */
}return p – info - 1;
}
学籍数据的输入int InputStudent( InfoStudent info[ ] ){ /* 输入数据到数组 info, 返回学生数量 */
int n = 4;char buf[ 256 ];InfoStudent *p;for( p=info; n == 4; p++ ) {gets( buf ); /* 读入一行 */n = sscanf( buf, “%s%ld%s%s”,p->student, &p->no, p->college, p->class );} /* 无足够输入时终止 */return p – info - 1;
}
查找学籍数据的实现InfoStudent *GetStudent( long no,
InfoStudent info[ ], int n ){
/* 获得身份证号为 no 的学生的学籍数据 */int i;for( i=0; i<n; i++ ) {
if( info[ i ]->no == no )return &info[ i ];
}return NULL;
}
设计方法 功能分解
按照系统功能,逐步求精 使用函数进行功能抽象
模块化 数据抽象 围绕数据组织程序模块 数据结构 + 相关函数
异常处理 考虑各种错误的可能性 特别是数据输入中的各种错误
格式化输入输出的说明 转换说明符( scanf 、 printf )
格式: %m.nd 宽度 m :用于整数和实数的输入、字符串的输入 精度 n :用于小数部分的输出位数、字符串的输出宽度
返回值 scanf : 匹配的数据项个数 printf : 输出的字符个数
& 表示取变量的内存单元地址 数组名本身是内存单元地址,不用 &
其他输入输出 字符输入
int getchar( ) 从标准输入读入一字符,返回 ASCII 值gets( char buf[ ] ) 从标准输入读入一行字符,返回数组名或 0
来自字符数组的格式化输入和输出 sscanf( char buf[ ], char fmt[ ], … ) sprintf( char buf[ ], char fmt[ ], … )
各种数据类型的表示范围类型 范围 内存char -128, 127 1byteunsigned char 0,255 1byteshort -32768,32767 2byteunsigned short 0,65535 2bytelong -2147483648, 2147483647 4byteunsigned long 0,4294967295 4bytefloat -3.4E+38, 3.4E+38 4bytedouble -1.7E+308, 1.7E+308 8byte
模块化程序的文件组织 标准的组织方法
将所有结构定义、函数原型放在某个头文件中( *.h ) 将每个模块的函数定义和全局变量放在同一个源程序文件中( *.c ) 每个源程序文件中,添加 #include “ 头文件名” 建立工程文件,统一管理所有源程序文件
7.3 动态数据结构与链表 数组的应用条件:
必须预先确定上限 无法适应元素个数可变的应用需求
解决方案 采用动态数据结构 根据需求,在运行中申请存储空间
常用的动态数据结构 链表、树、图等
链表的概念 链表是利用指针将一组数据组成起来的数据结构:
指针提供了查找数据的手段
表头
数据 指针 数据 指针 数据 NULL
学籍信息链表struct Link { /* 链表节点结构 */InfoStudent data; /* 学籍信息 */struct Link *next; /* 指向下一节点 */};使用特征 构造时,逐个建立节点和连接关系 访问时,从表头逐个查找 增加或删除数据时,仅改变指针连接关系
例 7-4 :一个读入学籍信息构造链表的函数 函数原型
struct Link *InputStudent(void) 从 stdin 读数据、返回链表表头
数据对象 表头指针 p 当前节点指针 q 读入的学籍信息 x
读取一行学籍信息
读成功?
返回表头 添加到表头
表头置空
Y
N
程序实现
struct Link *InputStudent( void ) {char buf[256]; /* 输入数据构造链表,返回表头指针 */InfoStudent x;struct Link *p = NULL, *q;while( 1 ) {gets( buf ); /* 读取一行字符 */if( 4 != sscanf( buf, “%s%ld%s%s”,x.student, &x.no, x.college, x.class ) ) return p; /* 无足够数据则终止 */q = (struct Link *)malloc(sizeof(struct Link));if( NULL == q ) return p;q->data = x; q->next = p;p = q; /* 加在表头 */
}}
例 7-5 :学籍管理系统 提供学生信息输入,信息输出和指定信息删除的功能 功能分解:
命令输入(主控) 信息输入( InputStudent ) 信息输出( OutputStudent ) 指定信息删除
数据对象和主控逻辑 数据对象
一组学生信息(数量不定) 采用链表实现(易于维护) 表头 pHead 命令 c 学号 no
命令输入C
信息删除
信息输入
信息输出
C=1
C=2
C=3输入学号 n
函数抽象的运用 int GetCommand( void );
读取用户命令,返回 struct Link *InputStudent( void );
负责信息输入(输入一组学籍数据,组织成链表) void OutputStudent( struct Link *p );
负责信息输出(输出链表中的所有学籍数据) struct Link *RemoveStudent( struct Link *p, char *n );
负责信息删除 删除学号 n 指定的学生信息,返回修改后的链表
void DeleteLink( struct Link *p ); 释放各个节点占用的空间
主控逻辑的实现main( ) {struct Link *pHead = NULL;int c; char no[32];while( 1 ) {c = GetCommand( ); /* 命令输入 */switch( c ) {case 1: pHead = InputStudent( );break;/* 信息输入 */case 2: OutputStudent( pHead );break;/* 信息输出 */case 3: scanf( “%s”, no );pHead = RemoveStudent( pHead, no );break;/* 信息删除 */default: DeleteLink( pHead );return 0; /* 释放链表空间 */
} } }
命令输入的实现int GetCommand( void ) {int c;do {
printf( “Select an function as follow:\n” );printf( “1 for Input\n” );printf( “2 for Output\n” );printf( “3 for Remove\n” );printf( “0 for Exit\n” );printf( “Input(0-3): “ );scanf( “%d”, &c );
} while( c < 0 || c > 3 ); /* 消除非法输入 */return c;
}
信息输出(遍历链表)void OutputStudent( struct Link *p ) {
while( p != NULL ) {printf( “\n%s, %ld, %s, %s”,p->data.student, /* 学号 */p->data.no, /* 身份证号 */p->data.college, /* 学院 */p->data.class ); /* 班级 */p = p->next; /* 指向下一节点 */
} }
链表的处理方法 元素的查找方法
沿着节点指针,逐个查找 比数组效率低
元素的删除方法 改变元素的指针连接关系 比数组效率高
元素的插入 选择位置,改变指针连接关系 比数组效率高
信息删除(链表元素的删除)
struct Link *RemoveStudent( struct Link *p, /* 表头指针 */char *n ) /* 学号 */
{struct Link *q;if( NULL == p )return NULL;if( 0 == strcmp(p->data.student, n) ) {
q = p; /* 头元素 = 指定学生 */p = p->next; /* 指向其余元素 */delete q; /* 释放头元素空间 */} elsep->next = RemoveStudent( p->next, n );/* 在其余表中删除(递归法) */return p; /* 修改后的链表 */
}
动态数据结构的释放void DeleteLink( struct Link *p ) {
struct Link *q;while( p != NULL ) {
q = p;p = p->next; /* 指向下一节点 */delete q; /* 释放当前节点 */
}}
动态数据结构的使用 应用中的问题
面向复杂算法和复杂数据结构 动态数据结构的全局性破坏信息隐蔽 出现专门用于组织数据的数据指针
如何控制复杂性 使用抽象手段 模块化、函数抽象、局部化 仍需要研究新的抽象手段
本章作业 阅读第十章
完成自我测验练习 10.3 10.4 程序设计练习
12.7 12.91. 为时间(小时、分、秒)的保存设计一个结构;编写程序,读入形如 21:32:04 的输入,保存在结构中,检查输入数据的正确性。2. 修改程序例 7-3 :在学籍信息中添加入学日期,修改程序中相应的部分。
上机题 修改例 7-4 中函数 InputStudent ,保证链表中各个学生的信息是按照学号升序的顺序组织的 连同例 7-5 完成程序测试。