需求分析
1) 问题描述
读入一个C程序,统计程序中代码、注释和空行数以及函数的个数和平均行数,并利用统计信息分析评价该程序风格。
2) 基本要求如下:
(1)、把C程序文件按字符顺序读入源程序;(2)、边读入程序,边识别统计代码行、注释行和空行,同时还要识别函数的开始和结束,以便统计其个数及平均行数。(3)、程序风格分为代码注释和空行三方面。每方面分A、B、C、D四个等级。
A B C D
代码(函数的平均长度) 10~15行 8~9或16~20 5~7或21~24 <5或>24
注释(占总行数比例) 15~25% 10~14或26~30% 5~9或31~35% <5%或>35%
空行(占总行数比率) 15~25% 10~14或26~30% 5~9或31~35% <5%或>35%
3)输入输出范例
以下是对程序文件ProgAnal.C分析的输出结果示例: The results of analysing program file "ProgAnal.C": Lines of code :180 Lines of comments: 63 Blank lines: 52 Code Comments Space 61% 21% 18% The program includes 9 functions. The average length of a section of code is 12.9 lines. Grade A: Excellent routine size style. Grade A: Excellent commenting style. Grade A: Excellent white space style.
1. 概要设计
1).头文件引用与宏定义:
#include
#include
#include
#define TRUE 1
#define FALSE 0
#define BOOL int
#define MAXSIZE 5000
#define COUNT 20 // 可以统计的最大的文件个数
#define LEN 20 // 文件名的最大长度
2).所用存储结构
//函数属性结构
typedef struct {
char filename[20]; //每一个函数的名字
int length; //每一个函数的长度
int pos; //每一个函数的位置
}Fun;
//统计结构的声明
typedef struct {
int comments; //纯注释的个数
int comment; //混合注释个数
int blank; //空行的个数
Fun fun[MAXSIZE]; //函数的属性
int others; //除去函数中代码外其余的代码个数
int funcount; //函数的个数
}Analy;
2. 详细设计
1).函数功能及声明
BOOL StrEmpty(char *s)//S是不是空
int Find(char *s1,char *s2)//查找S1中是否有值为S2的子串
void HaveLine(FILE *fp,char *s)//重文件中获取一行
char* IgnoreB(char *s)//截断一行的空字符
int IsCom(char *s)//判断一行是不是注释
BOOL IsBlank(char *s)//判断一行是不是空格
BOOL IsFunB(char *s)//判断一行是否是函数的开头
void PrintMax(Analy *An)//打印最大函数的信息
void printR(int aver ,int comc,int blanks )//打印代码风格级别
void print(Analy *An)//
void checkfile(char *filename,int i)//检测文件是否存在
BOOL GetIn(int *n)//规范输入的数据,只能为数字
void analy(char filename[COUNT][LEN],int n)//分析单个文件
void savelog()//保存日志
2)详细源代码
//检测是否为空串
BOOL StrEmpty(char *s)
{
if(s[0]=='\0')
return TRUE;
return FALSE;
}
//查看S1中是否有值为S2的子串,若有则返回第一个子串的位置,若无则返回-1;
int Find(char *s1,char *s2)
{
int i = 0,j = 0;
if(strlen(s1) < strlen(s2))
return -1;
while (s1[i]!='\0')
{
if(s1[i] == s2[j])
{
i++;
j++;
if(s2[j]=='\0')
return i-j;
continue;
}
i++;
j=0;
}
return -1;
}
//读取文件中的一行字符
void HaveLine(FILE *fp,char *s)
{
while(!feof(fp))
{
*s = fgetc(fp);
if(*s=='\n'){ //若果是一行的结尾则表示取完了一行
*s='\0';
return;
}
s++;
}
*s = '\0';
}
//忽略一行字符开头的空格和tab,返回截断后上的串指针
char* IgnoreB(char *s)
{
while (*s== ' ' || *s== ' ')
s++;
return s;
}
//判断一行字符是不是注释
int IsCom(char *s)
{
int posc,pos1,pos2;
s= IgnoreB(s);
posc = Find(s,"//");
if(posc == 0)//此行仅有注释,无代码;
return 1;
if(posc == -1)
return 0;
pos1 = Find(s,"\"");
pos2 = Find(&s[pos1+1],"\"");
if(posc > pos1 && posc < pos2)
return 0;
return 2;
}
//判断一行字符是不是空白
BOOL IsBlank(char *s)
{
s= IgnoreB(s);
if(*s== '\0')
return TRUE;
return FALSE;
}
BOOL IsFunB(char *s)
{
int i,j,pos,pos2;
//有分号,if while for的不是函数开头
if(Find(s,";") != -1 || Find(s,"if") != -1 || Find(s,"for") != -1 || Find(s,"while") != -1||Find(s,"switch") != -1)
return FALSE;
//没有小括号的不是函数开头
if((pos = Find(s,"(")) == -1)
return FALSE;
s = IgnoreB(s);
i = Find(s," ");
j = Find(s," ");
if (i != -1 && j != -1)
i = i > j ? j : i;
else if (i == -1)
i = j;
else if (1 == -1 && j == -1)
return FALSE;
if (i > pos)
return FALSE;
s = &s[i];
s = IgnoreB(s);
pos2 = Find(s,"(");
if(*s == '\0' || *s == '(' ||pos2 > pos)
return FALSE;
return TRUE;
}
//打印最大的函数属性
void PrintMax(Analy *An)
{
// FILE *fp;
int i,j = 0;
// int len;
for(i = 1;i < An->funcount ;i++)
if(An->fun[j].length < An->fun[i].length)//找出最大的函数的位置
j = i;
if(An->fun[j].length < 0 || An->fun[j].pos < 0){
printf(" there are not any function in the files");//打印出函数所在的文件和位置
return;
}
printf(" The length of lengthest function have %d lines \n",An->fun[j].length);//打印长度
printf(" The pos of lengthest function is in %s the %dth line\n",An->fun[j].filename,An->fun[j].pos);//打印出函数所在的文件和位置
printf(" The length of lengthest function have %d lines \n",An->fun[j].length);//打印长度
}
void printR(int aver ,int comc,int blanks )
{
//按代码级别判定标准输出分析结果
int i;
char Grade[4][15] = {"Excellent","Good","So-So","Bad"};//定义四个级别段
//判定代码的级别
if (aver <= 15 && aver >=10)
i = 0;
else if((aver <=20 && aver >= 16) || aver <= 9 && aver >= 8)
i = 1;
else if((aver <=7 && aver >= 5) || aver <= 24 && aver >=21)
i = 2;
else if ((aver <5) || (aver > 24))
i = 3;
printf(" Grade %s routine code style\n",Grade[i]);
//判定注释的级别
if (comc<= 25 && comc >= 15)
i = 0;
else if((comc <=14 && comc >= 10) || comc <= 30 && comc >=26)
i = 1;
else if((comc <=9 && comc >= 5) || comc <= 35 && comc >=31)
i = 2;
else if((comc <5) || (comc > 35))
i = 3;
printf(" Grade %s routine commenting style\n",Grade[i]);
//判定空行的级别
if (blanks <= 25 && blanks >= 15)
i = 0;
else if((blanks <=14 && blanks >= 10) || blanks <= 30 && blanks >=26)
i = 1;
else if((blanks <=9 && blanks >= 5) || blanks <= 35 && blanks >=31)
i = 2;
else if((blanks <5) || (blanks > 35))
i = 3;
printf(" Grade %s white space style\n",Grade[i]);
}
//打印输出结果
void print(Analy *An)
{
int sum = 0,funcode = 0;
int i, comc , blanks, aver ,code;
for(i = 0;i < An->funcount ;i++)//求函数的代码总数
funcode += An->fun[i].length;
//求所有的代码总数
sum += An->blank;
sum += An->comments;
sum += An->comment;
sum += An->others;
sum += funcode;
if(sum == 0)//防止除数sum为0
sum = 1;
if(An->funcount == 0)//防止除数m为0
aver = 0;
else
aver = funcode/An->funcount;
comc = (An->comments + An->comment)*100/sum;
blanks = ((An->blank)*100)/sum;
code = 100 - comc - blanks; //((funcode + An->others)*100)/sum;
printf(" The results of analysing program file:\n\n");
printf(" Lines of code: %d\n",sum - An->blank - An->comment - An->comments);
printf(" Lines of comments: %d\n",An->comments + An->comment);
printf(" Blank lines: %d\n",An->blank);
printf(" Code Comments Space\n");
printf(" _____ ________ _____\n");
printf(" ----- -------- -----\n");
printf(" %d%% %d%% %d%%\n",code,comc,blanks);
printf(" The program includes %d functions\n",An->funcount);
printf(" The average length of section of function is %d\n",aver);
PrintMax(An);
printf("\n");
//按代码级别判定标准输出分析结果
printR(aver,comc,blanks);
}
void checkfile(char *filename,int i)
{
FILE *fp;
while((fp = fopen(filename,"r")) == NULL)
{
printf("文件不存在 %s\n",filename);
printf("\n请重新输入第%d个源文件: ",i+1);
scanf("%s",filename);
}
}
BOOL GetIn(int *n)//规范输入的数据,只能为数字
{
char c;
*n = 0;
fflush(stdin);
c = getchar();//当输入一串数据并按回车后,getchar()取出缓存队列中的第一个字符
while(c != '\n'){
if(c == '0')
printf("输入有误!请重新输入....\n");
if(c >= '0' && c <= '9')
*n = (*n) * 10 +c - 48;
else{
printf("输入有误!请重新输入....\n");
fflush(stdin);//清空(刷新)缓存里的内容,以防被下次getchar()取用
return FALSE;
}
c = getchar();
}
return TRUE;
}
void analy(char filename[COUNT][LEN],int n)
{
FILE *fp;//分析源文件指针
FILE *fpp;//日志文件指针
Analy An;//程序统计结构体
char s[200];//存储每行的文件
BOOL begin = 0,start = 0;//设置函数开始标记
int i,j = -1,pos = 0;//函数的位置 长度信息
//c检测函数内大括号的匹配,comtype是注释的类型
int c=0,comtype;
An.blank = 0;
An.comments = 0;
An.comment = 0;
An.others = 0;
An.funcount = 0;
if((fpp = fopen("log.txt","w")) == NULL)//建立日志文件
printf("cannot open the file %s\n",filename[i]);
for (i = 0 ;i < n; i++)//遍历所有的文件
{
if((fp = fopen(filename[i],"r")) == NULL)
{
printf("cannot open the file %s\n",filename[i]);
getchar();
exit(0);
}
pos = 0; //函数在新一个文件中的位置初始化
while (!feof(fp))
{
HaveLine(fp,s); //从文件中读取一行数据
pos++; //每个函数在文件中开始的位置
//分类统计文件中的代码个数
comtype = IsCom(s);
if (comtype ==1)
{
An.comment++;
continue;
}
if (comtype == 2)
An.comments++;
if (IsFunB(s))
{
fprintf(fpp,"%s \n",s);//提取每个函数的名字写入到文件
j++; //j为函数的个数-1;
c = 0;//大括号个数初始化为0
begin = TRUE;
strcpy(An.fun[j].filename,filename[i]); //记录函数在哪个文件中
An.fun[j].pos = pos;
An.fun[j].length = 1;
}
else if(IsBlank(s))
An.blank++;
else if(begin){
An.fun[j].length++;
if (Find(s,"{") != -1) //检测是否进入到了函数体内
{
c++;
start = TRUE;
}
if (Find(s,"}") != -1) //检测是否一个函数的结束
c--;
if (c==0 && start)
{
begin = FALSE;
start = FALSE;
}
}
else
An.others++;
}
fclose(fp);//关闭分析文件
}
fclose(fpp);//关闭日志文件
An.funcount = j+1; //把函数的个数保存
print(&An); //打印分析结果
}
3)主函数
void main()
{
int n,i;
char c;
char filename[COUNT][LEN];
printf("\t-------------------------------------------------------------\n");
printf("\t 计算机学院网络工程三班*--------*张菲*--*学号3107007062\n");
printf("\t--------------------------------------------------------------\n");
while(1){
n=0;
printf("\t\t\tIII-----程序分析----III \n");
printf("请输入要分析的源文件的个数: ");
while(!GetIn(&n) || n <= 0){
printf("请输入要分析的源文件的个数: ");
}
for(i = 0 ;i < n ;i++)
{
printf("\n请输入第%d个源文件: ",i+1);
scanf("%s",filename[i]);
checkfile(filename[i],i);
fflush(stdin);
}
analy(filename,n);
printf("\t*************************是否继续使用? Y/N***************************\n");
c = getchar();
if(c == 'Y' || c == 'y')
continue;
break;
}
}
3. 调试分析
1) 经验收获
这个课程设计总体上难度不大,但细节问题很多,尤其是频繁的用到了文件的读写操作,牵扯到了关于缓存的一些细节问题,通过这次的课程设计,使我对C语言有了较深刻的了解,学习了很多以往不知道了语言特性,接触到了很多新的功能强大的函数,特别是对文件的读写有了比较熟练的掌握,知道了其基本的原理,与控制方法,本课程设计的一个难点是关于函数开头的判断问题,尝试了很多种的方法但是都不尽人意,最后经过大量数据的测试,利用了函数定义特性的格式与关键字排除方法相结合,基本上能应对常见的程序。在完成课程设计的过程中还有一点较大的收获是对编程环境的进一步的认识,这次编程用的是教熟悉的VC,以往用过TC,他们有很多的不同的地方,例如有写TC中可以用的函数,特别是在COIN.H目录下的函数,在VC中是不能用的,这给编程带来了一定的麻烦,但是通过网上查阅资料,都一一解决了,在这个过程中就是对编程工具的比对认识。
2)问题分析
纵观整个程序,完成了题目要求的全部功能,另外增加了很多的题目以外的功能,这些功能都是有着很大的实际用途的,例如题目中为了使这个课程设计难度降低,规定了一行文件只能是代码,注释或者空行,但是实际的程序中代码后面紧跟着注释是很常见的,而且是一中很好的编程风格。另外本程序可以对多个庞大的文件一起分析,对于几万行的程序分析的结果是比较确的,从而增强了程序的实际用途,本程序还能把要测试的代码文件中所有的函数声明提取出来,并保存为日志文件。
3)未来展望
由于时间和经验的关系,本程序还有很多不如人意的地方,以后将继续增加一些分析功能,例如,对文件中每个函数的信息,包括行数,位置,声明都按一定的格式保存到日志文件中,在就是把整个文件的分析结果也保存起来,以便与如后的查询。
五.操作演示及结果截图
1. 输入文件名,当找不到文件时的情况
2. 输入数据非法的情况
3.
4. 生成的日志文件(其中包含所有的函数声明)
5. 测试结果
1) 对本软件代码进行测试(小型源文件的测试)
说明:本程序的源文件有两个分别是analy.h和analy.c;
测试结果如图所示:
2) 对开发工具里的源代码进行测试(中型源文件的测试,1万行左右)
3) 对开发工具里的源代码进行测试(大型源文件的测试,2万行以上)
本文来源:https://www.2haoxitong.net/k/doc/fbb81eea4afe04a1b071defe.html
文档为doc格式