**** ****
学 号: ***********
院 (系): 计算机学院
专 业: 网络工程
**** *********
2013年 1月5日
第4题:文件目录管理与显示
1.需求分析
1.1问题描述:给出目录和文件信息,要求编程实现将其排列成一棵有一定缩进的树。给出目录和文件信息(如图 A ),要求编程实现将其排列成一颗有一定缩进的树(如图B )
其中,“\\ ”表示根目录,A、B 是其下面的子目录,c.txt 是其下面的文件;类似
地,AA1 、AA2 是A 下面的子目录,aa1.doc 是AA1 下的文件。
1.2基本要求:
(1)设计文件和目录信息树的存储结构。
(2)从文件或键盘输入目录和文件信息,输入格式采用绝对路径法,即:
\\A
\\A\\AA1
\\A\\AA1\\aa1.doc
…
创建时要检查同一路径下不能有同名的目录或文件名。
(3)设计文件和目录信息树的输出格式(以凹入表的形式显示)。
(4)查找指定目录和文件。
(5)添加新目录或新文件。
(6)删除指定目录或文件,子目录能够被删除的前提是其为空,既不包含任何子目录和文件;根目录不能删除。
(7)扩充目录或文件信息,如创建时间、读写权限、文件长度或子目录包含的子目录和文件数等。
(8)对同一层次下的子目录或文件按创建时间有序输出。
(9)通配符的使用。如用“?”代表任意一个字符,用“*”表示任意多个多个任意字符。
扩展内容:
实现相对路径表示法。
1.3对课程设计成果的要求〔包括图表、实物等硬件要求〕:
报告纸质双面打印,后面附有报告内容及格式要求,不打印源程序;同时,每个同学提交报告和程序的电子档,以班级为单位刻录光盘。详见2012年秋193111-3DS课设安排。
2.设计
2.1 设计思想
2.1.1数据结构设计—逻辑结构设计
在存储结构方法上,采用二叉链表法,为简化对目录或文件的称呼,以下统称为
结点。
每个目录和文件都有基本信息,根据要求建立结构体,如下:
文件创建时间信息结构:
typedef struct creat_time
{
int year;
int month;
int date;
}creat_time;
文件结构体:
typedef struct info
{
int flag;
int cf;//有0和1两个值,为0时代表目录,为1时代表文件
char name[20];//文件名
char filetype[10];//文件类型
creat_time time;
struct info *parent,*firstchild,*brother;
int n;
int cenci;//层次
int size;//文件个数
}info;
2.2.2算法设计
(1) 创建根目录
a. 申请结点,输入基本信息;
b.所有指针置空;
(2)结点创建
P 代表母结点
a..判断所给结点是否为目录。不是,则给出失败信息;
b.查看是否在同一层有重名,若有,给出失败信息;
c. 申请结点,完善基本信息;
e. 根据母目录确定是否为孩子或孩子兄弟成员,建立链接关系;
f.结点孩子指针,兄弟指针置空;
q->firstchild=NULL;
q->brother=NULL;
(3)根据所给结点名称与同一层存在的结点名称比较,定位到所需结点;
info*Compare(char*ch,info*p)
//同级指针,根据文件名在同级目录里查找指定文件;
a.判断是否为根结点;
b. 回到该层次的第一个结点,即结点父亲的firstchild 指向结点;
c.依次比较,确定位置,若不存在,返回NULL ;
(4)绝对路径法提取目录名或文件名,定位指定结点
\\****\\****\\****\\***
a. 由该层内的应该用户给出的结点名称,提取字符段;
b.根据该段字符在该层次内查找定位到存在的结点,若不存在,给出失败信
息
c.提取下一段字符,到S2 继续比较;直到结束符*;
(5)树形展开目录
分析:对数据结构设计中图C 进行顺时针45 度旋转得到下图D
Parent
L1
Brother info Firstchild
L2
Parent Parent
Brother info Firstchild Brother info Firstchild
L1
忽略指针L1 、L2 ,可以发现这是一颗二叉树;故图A 或图B 可以构成二叉树,
图形如下: A
B AA1
c.txt AA2 aa1.doc
在这课二叉树中,左孩子与其双亲点在图A 或图B 中处于同一层,
(6)展开目录下的所有结点
A
B
C .txt
D
a..判断是否有下一层
b.输出结点孩子名称;
c.循环输出结点孩子的兄弟;
(7)删除指定目录或文件
后删 先删
以它作为AA1
A 的子树根结点
AA1
BB
aa1.doc
AA2
B
c.txt
(8)关于结点输出
a.输出结点名;
b..如果为文件,输出文件类型
2.2 大概设计
2.2.1函数调用关系图
(略)
2.2.2函数接口规格说明
(1)void NodeShow(info *q)
输出结点q的文件名和文件类型。
(2)info * Compare(char *ch,info *p)
比较输入的字符串ch与地址p是否一致
(3)void unfold(info * p)
展开目录p下的所有信息。
(4)void Creat(info *p)
以p为根结点创建目录或文件。
(5)void rootcreat(info *p)
创建树的根节点p,并把所有指针置为NULL。
(6)info *locate(info *p)
查找位置。根据该层用户给出的结点名称,提取相应的字符段,根据该段字符在层次内进行查找,定位到存在的结点,如不存在,给出失败的提示
(7)int Delete(info *p) 删除结点p。
(8)void Traverser(info *p)
树形展开目录p下所有信息。
(9)void Information(info *p)
输出文件或目录的详细信息。
2.3 详细设计
2.3.1添加新文件或目录
(1)结点创建:判断所给结点是否为目录。不是,则给出失败信息;
if(p->cf) {
printf("Can't add the file in file!\\n");
return;
}
查看是否在同一层有重名,若有,给出失败信息;
if(p->firstchild)
w=Compare(q->name,p->firstchild);
if(w)
{
if(strcmp(q->filetype,w->filetype))
printf("exist,can't add\\n");
(2)创建根目录:判断添加的类型
if(q->cf)//输入1,添加文件
{
printf("Input the filetype:");
scanf("%s",q->filetype);
printf("Input the size:");
scanf("%d",&q->size);
getchar();
}
if(!q->cf)//输入0,添加目录
{
q->filetype[0]='\\0';
q->size=0;
}
(3)申请结点,完善基本信息;所有指针置空;
q->time.year=00;
q->time.month=00;
q->time.date=date++;
q->flag=0;
q->parent=p;
q->firstchild=NULL;
q->brother=NULL;
q->cenci=p->cenci+1;
q->n=0;
p->n++;
if(!p->firstchild)
{
p->firstchild=q;//如果p的第一个孩子为空,则此时的q即为p的firstchild
}
else
{
p=p->firstchild;//如果p的第一个孩子不为空,则p指向firstchild
while(p->brother)//如果p的孩子的兄弟不为空,则p指向brother
{
p=p->brother;
}
p->brother=q;//如果p的孩子的兄弟为空,则此时的q即为p的brother
}
printf("add sucessful!\\n");
2.3.2显示功能
(1)展开目录下的所有信息。以凹入表的形式显示。先展开根结点信息,再展开firstchild的信息,最后展开brother的信息。
void NodeShow(info *q)//输出文件名和文件类型
{
if(q)
{
printf("%s",q->name);//输出文件名
if(q->cf)
printf(".%s",q->filetype);//输出文件类型
}
}
void Traverser(info *p)//树形展开目录
{
int i;
info *q=p;
if(!p) return;
for(i=0;i { printf(" "); } NodeShow(q); printf("\\n"); Traverser(p->firstchild);//递归调用 Traverser(p->brother); //递归调用 } (2)展开显示功能 void unfold(info * p) { NodeShow(p);// 先展开根结点信息 printf(" contains:\\n"); if(!p->firstchild) { printf("No directory or file\\n"); return; } p=p->firstchild;//再令p指向firstchild if(p) { printf("\"); NodeShow(p); //再展开firstchild的信息 printf("\\n"); while(p->brother) { p=p->brother; //再令p指向brother printf("\"); NodeShow(p); //最后展开brother的信息 printf("\\n"); } } } 2.3.3查找目录或文件功能 比较输入的字符串与地址是否一致。先比较parent,再比较firstchild,再比较brother。 while(d!='#'&&d!='.') { if(d=='\\\\')//根据该层用户给出的结点名称,提取相应的字符段,根据该段字符在层次内进行查找,定位到存在的结点,如不存在,给出失败的提示 { c[i]='\\0'; scanf("%c",&d); i=0; p=Compare(c,p); if(!p) { printf("path error!\\n");return NULL; } p=p->firstchild; } c[i++]=d; scanf("%c",&d); } if(d=='.') { i=0; scanf("%c",&d); while(d!='#')// 提取下一字符,返回,直到字符为#结束 { t[i++]=d; scanf("%c",&d); } t[i]='\\0'; } if(d=='#') { c[i]='\\0'; p=Compare(c,p); if(!p) { printf("directory path error!\\n");return NULL; } if(t[0]!='\\0'&&p->filetype[0]!='\\0') { if(strcmp(t,p->filetype)) { printf("file path error!\\n");return NULL; } return p; } else if(t[0]=='\\0'&&p->filetype[0]=='\\0') return p; } printf("ee path error!\\n");return NULL; } 2.3.4删除目录或文件功能 遍历目录,查找所需的文件或目录,删除文件。 void DELETE(info *p) { info *q; q=p->parent; if(p->firstchild) Delete(p->firstchild); if(q->firstchild==p) { q->firstchild=p->brother; free(p); } else { q=q->firstchild; while(q->brother!=p&&q->brother) q=q->brother; q->brother=p->brother; free(p); } } int Delete(info *p) { if(p) { Delete(p->brother); Delete(p->firstchild); free(p); } return 0; 3.调试分析 (1)主要面临的问题有以下几方面:从绝对路径中提取字符段 树形目录展开时缩进问题 其中第一个方面问题,发现调用库中函数不成功,于是通过对字符串复制、连接、比较几个功能练习一下解决了。 再说第二方面的问题,为了解决缩进问题,采用了几种方法,有的虽然有点那么回事,但总是同一层次的缩进量不一样,于是我想了个办法,决定在结点基本信息里添加一个数据,即记录结点所在的层次,根目录为0,往下依层次逐层加1,这样在DRL 遍历时,通过所在层次决定缩进量,即 缩进量 = 层次值 × 单位缩进量 其中,单位缩进量是常量,如:” ”,用两个空格表示,这样便解决 了同一层结点缩进量不一致的情况。对于这道题目,现在来看看的话,主要把握好以下几点:把题中树形结构转为二叉树结构,左孩子在原树中为其双亲结点的兄弟,右孩子在原树中为其双亲结点的第一个孩子会熟练掌握和运用二叉树解决问题。在现有条件下若不好解决问题,可以适当创造一些条件,尝试其他思维。 (2)算法的时间空间复杂度分析;(略) 4.用户手册 4.1本程序的运行环境为windows下的c语言编译器如(vc,vs,dev),执行文件为:cpp2.cpp。 4.2进入程序后,即显示如下用户界面: 4.3请按照提示选择操作,需输入的文件格式为/T?*,?为用户自定义,每个后继操作均有明确提示。 4.4输入操作后,请按回车确认。 5.测试数据及测试结果测试数据及测试结果: 设计一张测试用例表,每个测试用例包括以下内容: 添加 2 123- (1)添加目录成功 (2)显示目录 (3)添加失败 (4)添加文件成功 (5)展开目录 6.设计体会与总结(合1题4题) 本次程序设计的总体思路明确,易懂,能够清楚的分辨出各模块的功能,利于用户的阅读、了解程序,该程序的执行过程是相当的易于读者使用,它会在每一步都提示用户接下来的输入数据。当然,本次课程设计还有许多的不足之处,在以后的不断学习当中我还会继续完善这个程序。 通过做课程设计,使我收获了很多东西,知识这方面说起,以前觉得不管什么样的题还是编程,只要了解算法的思想就行了,到时候用的时候自然就会发挥出来,可这次的课程设计却告诉我并不是这样的,我在此次课程设计的编程的时候就遇到了这样的问题。觉得自己了解算法思想就一定能编出来,可是事实却不得不又拿起书来继续研究,继续查找一些相关的资料,与同学老师之间交流,互相学习之后,才将程序基本编写出来,但运行过程又出现了一些问题,需要不断调试,在老师和同学的帮助下,程序最终无误执行出来了。 通过写报告我对自己的程序有了更进一步的了解,书面规范性有了较大程度的提高。 总体来说,这次数据结构课设让我的编程能力有了进一步提高,我会继续努力提升自己的素养,为自己的未来做更多的积淀。 参考文献:《数据结构———使用C语言(第四版)》—朱战立 《C程序设计(第三版)》———谭浩强 附:源程序清单 #include #include #include #include int date=0; typedef struct creat_time { }creat_time; typedef struct info { }info;//一个节点,包含当前目录信息,包括父节点,第一个孩子节点,兄弟节点等 void NodeShow(info *q)//显示节点信息,即展开目录 { printf("%s",q->name); if(q->cf) printf(".%s",q->filetype); } info * Compare(char *ch,info *p)//比较目录是否存在 { 跟父节点比较 i=strcmp(ch,p->name); if(!i) return p; return NULL; 跟第一个孩子节点比较 在目录里面,返回目录地址 跟兄弟节点比较 { p=p->brother; if(!strcmp(ch,p->name)) return p; } 不在目录里面,返回空 } void unfold(info * p)//展开目录 { 包括:\\n"); 没有此目录或文件\\n"); return; 第一个孩子节点 printf("\"); NodeShow(p); printf("\\n"); 兄弟节点 { p=p->brother; printf("\"); NodeShow(p); printf("\\n"); } } void Creat(info *p)//创建目录 { 不能添加文件!\\n"); return; 创建目录还是文件 输入名字:"); 创建文件步骤 输入文件类型:"); scanf("%s",q->filetype); 输入数量:"); scanf("%d",&q->size); getchar(); w=Compare(q->name,p->firstchild); if(!strcmp(q->filetype,w->filetype)) 已经存在,不能添加!\\n"); return; 创建目录步骤 q->filetype[0]='\\0'; q->size=0; 存地址,建立目录树形结构 p=p->firstchild; while(p->brother) { p=p->brother; } p->brother=q; 添加成功!\\n"); } void rootcreat(info *p)//创建根目录 { } info *locate(info *p)//定位目录位置,若目录有误则返回空 { 接收输入的目录路径 目录路径不为空以及不包含.,循环查找当前目录以及其子目录是否存在所输入目录 if(d=='\\\\') { c[i]='\\0'; scanf("%c",&d); i=0; p=Compare(c,p); if(!p) { 路径有误!\\n"); return NULL; } 对子目录进行查找 } c[i++]=d; scanf("%c",&d); i=0; scanf("%c",&d); while(d!='*') { t[i++]=d; scanf("%c",&d); } t[i]='\\0'; 路径结束标记 c[i]='\\0'; p=Compare(c,p); if(!p) { 目录路径有误!\\n"); return NULL; } if(t[0]!='\\0'&&p->filetype[0]!='\\0')//路径名称合法 { if(strcmp(t,p->filetype)) { 文件路径有误!\\n"); return NULL; } return p; } else if(t[0]=='\\0'&&p->filetype[0]=='\\0') return p; 路径有误!\\n"); } int Delete(info *p)//删除目录 { 删除兄弟节点 删除第一个孩子节点 释放指针 } void DELETE(info *p)//删除p所指向的所有文件和目录 { q->firstchild=p->brother; free(p); q=q->firstchild; while(q->brother!=p&&q->brother) q=q->brother; q->brother=p->brother; free(p); } void Traverser(info *p)//递归显示节点所包含文件和目录信息 { 缩进 显示节点信息 显示第一个孩子节点 显示兄弟节点 } void Information(info *p)//显示文件信息 { 文件类型:%s\\n",p->filetype); 大小:%dK\\n",p->size); 菜单\\n"); 创建时间:"); 包含的文件数量:%d\\n",p->n); } char menu()//选择菜单 { 添加目录或文件 |\\n"); 显示目录 |\\n"); 展开目录 |\\n"); 查询目录或文件 |\\n"); 删除目录或文件 |\\n"); 退出 |\\n"); 请选择:"); } int main() { choice=menu(); switch(choice) { case '1': 输入目录路径(end of '*'):"); p=locate(root); if(!p) break; Creat(p); break; case '2': Traverser(root); break; case '3': 输入目录路径(end of '*'):"); p=locate(root); if(p) unfold(p); getchar(); break; case '4': 输入目录路径(end of '*'):"); p=locate(root); if(p) { 找到了!\\n"); 浏览单词(Y\\\\N)?"); getchar(); c=getchar(); printf("\\n"); if(c=='y'||c=='Y') Information(p); } getchar(); break; case '5': 输入目录路径(end of '0'):"); p=locate(root); if(p) { if(p==root) { 根目录不能被删除!\\n"); getchar(); break; } DELETE(p); } getchar(); break; case '0': return 0; default: 选择有误!"); } }
程序运行结果图如下:测试输入 首先选择功能1 输入目录路径/T* 选择 名字123 选择功能 选择功能1 输入目录路径为/T/123* 输入目录路径为/T* 添加1个文件123.txt 选择功能3 删除文件123.txt 测试目的 设计该输入的目的在于看程序是否达到题目要求,并发现测试程序在哪方面可能存在漏洞,进而修正。 正确输出 按初始化格式输出 -- -- - -- -- -T -- -- -- -- 实际输出 按初始化格式输出 -- -- - -- -- 目录路径有误-- -- -- -- -- 错误原因 无 -- -- - -- -- 输入格式有误-- -- -- -- -- 当前状态 正确 正确 正确 正确 正确 正确 已修正 正确 正确 正确 正确