1 / 49

第七章 结构和模块化设计 7.1 结构

第七章 结构和模块化设计 7.1 结构. 复合数据对象的需求 描述由不同类型的数据元素组成的数据对象 例如: 教学管理系统中的学生数据 包括:姓名、性别、学号、班级 语言支持 C 语言中的结构 Pascal 语言中的记录 C++ 、 Java 语言中的类. 语法结构 struct 结构名 { 类型 成员变量; 。。。 }; /* 学生信息的结构 * / struct Student { char name[32]; char sex; int no; char class[8]; };.

Download Presentation

第七章 结构和模块化设计 7.1 结构

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第七章 结构和模块化设计7.1 结构 • 复合数据对象的需求 • 描述由不同类型的数据元素组成的数据对象 • 例如: • 教学管理系统中的学生数据 • 包括:姓名、性别、学号、班级 • 语言支持 • C 语言中的结构 • Pascal 语言中的记录 • C++ 、Java 语言中的类

  2. 语法结构 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; 结构定义和变量说明

  3. 结构变量及相关运算 • 结构变量占用的内存空间 • 大于等于各个元素占用内存空间之和 • 结构变量的运算 • 初始化 如:struct Student chen= { “chen”, ‘F’, 24, “9707” }; • 赋值 • 函数参数 • 结构分量的运算 • 引用: chen.name chen.sex • 运算取决于分量的类型

  4. 例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” ); }

  5. 程序读解 • 结构的使用 • 分量使用:各种运算、取地址、作为参数; • 分量类型:指针、其他结构(不允许递归定义) • unsigned char • 无符号字符(1个字节:0-255) • %d 指示读整数,存到该字节 • 数组 days 的使用 • 避免 switch 语句的多分支 • 执行速度高于 switch

  6. 例7-2:洗牌和发牌模拟 • 任务: • 考虑扑克牌的 4 个花色和 52 张,完成洗牌过程,通过输出完成发牌过程 • 基本思路 • 设置数据对象保存 52 张牌、4 个花色和牌名信息 • 产生随机数来控制牌的交换,模拟洗牌过程 • 输出所有牌,分为 4 组,模拟发牌过程

  7. 数据对象设计 52张牌 cards[52] 4个花色 suit[4] 13个牌号 face[13] 算法 初始化52张牌 洗牌过程 发牌过程 初始化 依次设置每张牌的花色和牌号 洗牌过程 依次处理每张牌(第 i 张) 产生一个随机数(0-51) j 将第 j 张和第 i 张牌交换 算法设计

  8. 程序结构设计 • 数据结构 • 采用 struct 描述扑克牌的信息(花色、牌号) • 采用整数数组表示牌号 1, 2, 3, … 13 • 采用字符数组表示花色 H, D, C, S • 程序结构 • 设置 3 个函数 fillDeck, shuffle, deal • 分别负责初始化、洗牌和发牌的完成

  9. 程序实现(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 ); } 时间函数头文件 数组初始化 获得当前时间 初始化随机数函数

  10. 程序实现(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 ]; } } 结构分量的引用

  11. 程序实现(3/3) 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’ ); } 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; } } 随机数生成 制表符 条件表达式

  12. 几点程序说明 • 结构数组 • 元素是一个完整的结构 • 结构变量的赋值 temp = desk[ i ] • 整个结构的复制(时间开销大) • 数组初始化 • 可利用初值个数设置数组大小 • 随机数的产生 • int rand( ) 返回随机数 • void srand( unsigned seed ) 初始化随机数生成 • time( ) 获得当前时间(单位:秒) • 需要通用函数和时间函数头文件 stdlib.h, time.h

  13. 虚拟机的概念 • 抽象的计算机 • 接受一组指令,按照给定的指令序列执行 • 虚拟机概念在程序设计中的应用 • 使得一个程序模块,按照类似的方式工作: • 以一组函数实现虚拟机的指令系统 • 以函数调用表示指令的执行 • 例如: • 上例中,扑克牌数组以及相关的 3 个函数组成一个虚拟机;3 个函数实现了该虚拟机的 3 个指令。 • 使用者(main函数)仅负责向虚拟机发送指令(函数调用),不必关系其内部实现细节

  14. 抽象方法与信息隐蔽 • 抽象方法的运用 • 通过 3 个函数的设置(fillDeck, shuffle, deal),完全隐蔽了扑克牌相关处理的内部计算逻辑; • 虚拟机使用者的视点 • 仅了解 3 个函数的使用方法,好象它们是系统提供的标准函数 • 对软件工程的支持 • 虚拟机的使用者和实现者可以是不同的开发组 • 分别实现大型软件的不同组成部分 • 软件模块接口就是一组函数原型及其说明

  15. 7.2 模块化程序设计 • 软件模块 • 数据对象及其相关处理算法 • 程序模块:数据结构及其相关函数 • 例 7-1 中的模块 • 主控模块:main函数 • 扑克牌模块:数组 deck 以及3个函数 • 信息隐蔽 • 模块的使用者无须连接数据对象的细节 如:main 函数编制时,无须了解数组 deck 的详细定义、数据内容和结构组织。

  16. 例7-3:简易学生管理系统 • 任务: • 分别输入学生的户籍信息和学籍信息,打印出学生基本信息表(假设学生人数<250人) • 户籍信息:姓名、身份证号码、出生年月日、住址; • 学籍信息:学号、身份证号码、所属学院、专业、班级 • 学生基本信息表:学号、姓名、年龄、所属学院、班级; • 数据对象 • 学生户籍数据、学生学籍数据 • 学生基本信息表可以直接输出(不保存)

  17. 算法设计 • 输入户籍数据 (每行输入一个学生的数据,空格分割各个项目) • 输入学籍数据 (每行输入一个学生的数据,空格分割各个项目) • 构造并输出学生基本信息表 (提取户籍和学籍数据,构造并输出学生信息表)

  18. 算法的逐步求精 • 1、2 步 • 仅涉及输入输出,忽略算法描述 • 3 构造学生基本信息表 • 3.1 依次从户籍数据中取出一个学生的信息 • 3.2 根据其身份证号码,找出该生的学籍信息 • 3.3 综合该生的户籍信息和学籍信息,构造基本信息记录,填入学生基本信息表 • 3.4 重复 3.1-3.3 的处理,直至处理完所有学生的数据

  19. 程序结构设计 • 数据对象 • 户籍数据、学籍数据、学生基本信息表 • 尽可能符合信息的原始结构、采用struct • 采用结构数组来保存数据 • 模块与函数的设计 • 围绕户籍数据,提供输入、依次提取的函数 • 围绕学籍数据,提供输入、查找的函数 • 为学生基本信息,提供构造并输出的函数 • 形成 3 个程序模块

  20. 程序结构 主控模块 main( ) 户籍数据模块 InfoAddr 数据接口 InputAddr( … ) 输入数据 学籍数据模块 InfoStudent 学籍数据 InputStudent( … ) 输入数据 GetStudent( … ) 查找学生信息 基本信息表模块 Output( … ) 基本信息表的输出

  21. 户籍处理模块的设计 #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, 返回学生数量 */

  22. 学籍处理模块的设计 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是学生数量 */

  23. 主控模块的设计 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 ); /* 输出基本信息表 */ }

  24. 设计说明 • 函数原型设计 • 考虑函数内部功能的实现中,需要用到的所有输入输出信息; • 将输入信息作为参数;输出结果作为返回值,或通过参数返回; • 确保信息处理的局部化 • 实现技术细节 • 对于复杂结构,利用结构,结构分量的结构 • 对于大的数据对象(结构数组),采用静态变量

  25. 学生基本信息表的构造和输出 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 ); } }

  26. 户籍信息的输入 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; }

  27. 学籍数据的输入 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; }

  28. 查找学籍数据的实现 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; }

  29. 设计方法 • 功能分解 • 按照系统功能,逐步求精 • 使用函数进行功能抽象 • 模块化 • 数据抽象 • 围绕数据组织程序模块 • 数据结构 + 相关函数 • 异常处理 • 考虑各种错误的可能性 • 特别是数据输入中的各种错误

  30. 格式化输入输出的说明 • 转换说明符(scanf、printf) • 格式: %m.nd • 宽度m:用于整数和实数的输入、字符串的输入 • 精度n:用于小数部分的输出位数、字符串的输出宽度 • 返回值 • scanf: 匹配的数据项个数 • printf: 输出的字符个数 • & 表示取变量的内存单元地址 • 数组名本身是内存单元地址,不用&

  31. 其他输入输出 • 字符输入 int getchar( ) • 从标准输入读入一字符,返回ASCII值 gets( char buf[ ] ) • 从标准输入读入一行字符,返回数组名或0 • 来自字符数组的格式化输入和输出 • sscanf( char buf[ ], char fmt[ ], … ) • sprintf( char buf[ ], char fmt[ ], … )

  32. 各种数据类型的表示范围

  33. 模块化程序的文件组织 • 标准的组织方法 • 将所有结构定义、函数原型放在某个头文件中(*.h) • 将每个模块的函数定义和全局变量放在同一个源程序文件中(*.c) • 每个源程序文件中,添加 #include “头文件名” • 建立工程文件,统一管理所有源程序文件

  34. 7.3 动态数据结构与链表 • 数组的应用条件: • 必须预先确定上限 • 无法适应元素个数可变的应用需求 • 解决方案 • 采用动态数据结构 • 根据需求,在运行中申请存储空间 • 常用的动态数据结构 • 链表、树、图等

  35. 表头 数据 指针 数据 指针 数据 NULL 链表的概念 • 链表是利用指针将一组数据组成起来的数据结构: • 指针提供了查找数据的手段

  36. 学籍信息链表 struct Link { /* 链表节点结构 */ InfoStudent data; /* 学籍信息 */ struct Link *next; /* 指向下一节点 */ }; 使用特征 • 构造时,逐个建立节点和连接关系 • 访问时,从表头逐个查找 • 增加或删除数据时,仅改变指针连接关系

  37. 表头置空 读取一行 学籍信息 读成功? 返回表头 添加到 表头 例7-4:一个读入学籍信息构造链表的函数 • 函数原型 • struct Link *InputStudent(void) • 从 stdin 读数据、返回链表表头 • 数据对象 • 表头指针 p • 当前节点指针 q • 读入的学籍信息 x N Y

  38. 程序实现 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; /* 加在表头 */ } }

  39. 例7-5:学籍管理系统 • 提供学生信息输入,信息输出和指定信息删除的功能 • 功能分解: • 命令输入(主控) • 信息输入(InputStudent) • 信息输出(OutputStudent) • 指定信息删除

  40. 命令输入 C 信息输入 C=1 信息输出 C=2 输入学号n C=3 信息删除 数据对象和主控逻辑 • 数据对象 • 一组学生信息(数量不定) • 采用链表实现(易于维护) • 表头 pHead • 命令 c • 学号 no

  41. 函数抽象的运用 • 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 ); • 释放各个节点占用的空间

  42. 主控逻辑的实现 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; /* 释放链表空间 */ } } }

  43. 命令输入的实现 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; }

  44. 信息输出(遍历链表) 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; /* 指向下一节点 */ } }

  45. 链表的处理方法 • 元素的查找方法 • 沿着节点指针,逐个查找 • 比数组效率低 • 元素的删除方法 • 改变元素的指针连接关系 • 比数组效率高 • 元素的插入 • 选择位置,改变指针连接关系 • 比数组效率高

  46. 信息删除(链表元素的删除) 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; /* 释放头元素空间 */ } else p->next = RemoveStudent( p->next, n ); /* 在其余表中删除(递归法) */ return p; /* 修改后的链表 */ }

  47. 动态数据结构的释放 void DeleteLink( struct Link *p ) { struct Link *q; while( p != NULL ) { q = p; p = p->next; /* 指向下一节点 */ delete q; /* 释放当前节点 */ } }

  48. 动态数据结构的使用 • 应用中的问题 • 面向复杂算法和复杂数据结构 • 动态数据结构的全局性破坏信息隐蔽 • 出现专门用于组织数据的数据指针 • 如何控制复杂性 • 使用抽象手段 • 模块化、函数抽象、局部化 • 仍需要研究新的抽象手段

  49. 本章作业 • 阅读第十章 • 完成自我测验练习 10.3 10.4 • 程序设计练习 • 12.7 12.9 • 为时间(小时、分、秒)的保存设计一个结构;编写程序,读入形如 21:32:04 的输入,保存在结构中,检查输入数据的正确性。 • 修改程序例 7-3:在学籍信息中添加入学日期,修改程序中相应的部分。 • 上机题 • 修改例 7-4 中函数 InputStudent,保证链表中各个学生的信息是按照学号升序的顺序组织的 • 连同例 7-5 完成程序测试。

More Related