个人项目wc(C语言)
github地址:https://github.com/nilonger/mycangku
一、项目要求
1、wc.exe 是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有wc.exe 的功能,并加以扩充,给出某程序设计语言源文件的字符数、单词数和行数。
2、实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。
程序处理用户需求的模式为:wc.exe [parameter] [file_name]
3、基本功能:
wc.exe -c file.c //返回文件 file.c 的字符数 wc.exe -w file.c //返回文件 file.c 的词的数目 wc.exe -l file.c //返回文件 file.c 的行数
4、扩展功能:
-s 递归处理目录下符合条件的文件。 -a 返回更复杂的数据(代码行 / 空行 / 注释行)。 空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”。 代码行:本行包括多于一个字符的代码。 注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释: } //注释 在这种情况下,这一行属于注释行。 特殊情况,如 return; } //注释,既算是代码行也算是注释行。 [file_name]: 文件或目录名,可以处理一般通配符
5、高级功能:
-x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。
二、PSP
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
---|---|---|---|
Planning |
计划 |
50 |
80 |
· Estimate |
· 估计这个任务需要多少时间 |
500 |
600 |
Development |
开发 |
500 |
600 |
· Analysis |
· 需求分析 (包括学习新技术) |
100 |
200 |
· Design Spec |
· 生成设计文档 |
20 |
25 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
20 |
20 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
20 |
20 |
· Design |
· 具体设计 |
80
|
100 |
· Coding |
· 具体编码 |
150 |
350 |
· Code Review |
· 代码复审 |
30 |
60 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
30 |
60 |
Reporting |
报告 |
20 |
30 |
· Test Report |
· 测试报告 |
20 |
30 |
· Size Measurement |
· 计算工作量 |
10 |
10 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
10 |
20 |
|
合计 |
1560 |
2205 |
三、解题思路
1、刚开始拿到题目的时候,一脸懵逼,不知道从哪里开始着手,只知道要统计字符数,单词数,总行数。不知道怎么下手,就百度了一下wc,看到了一些用java,Python,C什么的例子,发现其实还而已,然后就开始着手计划打码
2、字符数:只要是在 ‘!’和‘~’ 之间的字符,都算入统计范围内
词数:要求在由英文字母组成 ‘A’~‘Z’ 和 ‘a’~‘z’,然后设置一个变量 flag,帮助判断行数是否要 +1
行数:行数最简单,只要判断换行符 ‘\n’ 就好了
返回更复杂数据 A :空行,注释行(包括多行注释),代码行,这个几个数据的统计没得参考,只能通过上面那几个数据的统计过程自己 写相应的算法
递归目录:没有。觉得C语言实现有点难,别的语言又不会。所以放弃。
四、设计实现过程
第一种(也是现在代码的运行顺序):
第二种(之前为了调试单独一个函数的功能):
五、代码说明
统计字符数函数CountC():
#include <stdio.h> #include<stdlib.h> #include <string.h> int x,y,z,k,konghang;//x字符数 ; y词数 ;z行数 ; k 注释行数 int m; //不是空文件时 m=1 //字符数 void CountC(char fname[]){ FILE* fp=NULL; if((fp=fopen(fname,"r"))==NULL) //不要 放在主函数里面 { printf("打不开文件\n"); exit(0); } char ch; ch=fgetc(fp); while(ch!=EOF) { if(ch>='!'&&ch<='~') //ASCII 从33到126 x++; ch=fgetc(fp); } if(x==0)m=0; //m=0 则为 空文件 fclose(fp); }
统计单词数函数CountW():
//词数 void CountW(char fname[]){ FILE* fp=NULL; if((fp=fopen(fname,"r"))==NULL) { printf("打不开文件\n"); exit(0); } char ch; int flag=0; //用于判断是否要 +1 ,flag要先置为0 ch=fgetc(fp); while(ch!=EOF) { if(!(ch>='A'&&ch<='Z'||ch>='a'&&ch<='z')){ //如果是非字母字符,让flag为0 ||ch=='.'||ch=='_' flag = 0; } else if((flag ==0)&&(ch>='A'&&ch<='Z'||ch>='a'&&ch<='z')) //如果是字母字符且flag原值为0,flag置1,单词数加一 { flag = 1; y++; } ch=fgetc(fp); } // y--; //单独测试 的时候要加上 ,不知道为什么要 -1 if(m==0) y=0; fclose(fp); }
统计行数CountL():
//行数 int CountL(char fname[]){ FILE* fp=NULL; if((fp=fopen(fname,"r"))==NULL) { printf("打不开文件\n"); exit(0); } char ch; ch=fgetc(fp); z++; // 单独测试 的时候要去掉 while(ch!=EOF) { if(ch=='\n') z++; ch=fgetc(fp); } if(m==0) z=0; //不能z-- 空文件运行会出错 fclose(fp); return(z); }
统计复杂类型数据函数CountA():
这里没有分开三个函数,而是合在一起。
缺点:见后面总结。
void CountA(char fname[]){ FILE* fp=NULL; if((fp=fopen(fname,"r"))==NULL) { printf("打不开文件\n"); exit(0); } //注释行 char ch; ch=fgetc(fp); while(ch!=EOF) { if(ch=='/') { ch=fgetc(fp); if(ch=='/') { k++; //注释行 +1 ch=fgetc(fp); //获得新的字符,并重新开始循环 continue; } else if(ch=='*') { ch=fgetc(fp); while(ch!=EOF) { if(ch=='\n') k++; //多行注释的 每个回车都要 +1 if(ch=='*') ch=fgetc(fp); if(ch=='/') { k++; //多行注释完整,行数+1,并跳出第二层循环 break; } ch=fgetc(fp); } } } ch=fgetc(fp); } printf("注释行:%d\n",k); rewind(fp); //指回文件开头 //空行 int flag=0; int num=0; //两个换行符之间的字符数 konghang=0; ch=fgetc(fp); while(ch!=EOF){ if(ch=='\n'&&num==0) //第一个换行符 { flag=1; konghang++; } else if(ch!='\n'&&flag==1) num++; else if(ch!='\n'&&flag==0&&num==0) //第一行 是代码行 的情况 { num++; flag=1; } else if(ch=='\n'&&flag==1&&num>=0){ //代码行到最后,把累加的字符数 归0 num=0; flag=0; } ch=fgetc(fp); } printf("空行数:%d\n",konghang); rewind(fp); //代码行 int num1=0,daimahang=0,flag1=1;; ch=fgetc(fp); while(ch!=EOF){ if((ch!=' '&&ch!='\n'&&ch!='\t'&&ch!='/')) { num1++; } else if(ch=='\n'&&num1>=1&&flag1==1) { daimahang++; num1=0; } else if(ch=='/') { ch=fgetc(fp); if(ch=='/') { ch=fgetc(fp); while(ch!=EOF){ if(ch=='\n') break; ch=fgetc(fp); } } else if(ch=='*') { ch=fgetc(fp); while(ch!=EOF){ if(ch>='A'&&ch<='Z'||ch>='a'&&ch<='z')num1++; if(ch=='\n')num1=0; if(ch=='*') { ch=fgetc(fp); if(ch=='/') break; } ch=fgetc(fp); } } } ch=fgetc(fp); } daimahang++; //最后一行没有'\n',自动 +1 if(m==0) daimahang=0; printf("代码行数:%d\n",daimahang); fclose(fp); }
主函数:
注释里面的就是 原本用来单独调试单个函数用的第二个流程。(选择 流程1 或 流程2 在运行过程中,同一个文件各数据统计的结果会有所出入,解决方法已经在各个函数里面备注了。)
int main(){ char fu[5]; char fname[50]; while(1){ m=1; //不是空文件是 m=1 x=y=z=k=konghang=0; printf("输入文件名:"); scanf("%s",fname); CountC(fname) ; printf("字符数为:%d\n",x); CountW(fname); printf("单词数为:%d\n",y); CountL(fname); printf("行数:%d\n",z); CountA(fname); } /* printf("-c:统计字符数\n -w:统计单词数\n -l:统计行数\n -a复杂数据\n -9:退出\n"); while(1) { x=0;y=1;z=1;k=0; //行数先设置为 1 printf("请输入你的操作c/w/l/a/9:"); scanf("%s",fu); if(fu[0]=='c') { CountC(fname) ; printf("字符数为:%d\n",x); } if(fu[0]=='w') { CountW(fname); printf("单词数为:%d\n",y); } if(fu[0]=='l') { CountL(fname); printf("行数:%d\n",z); } if(fu[0]=='a') { CountA(fname); } if(fu[0]=='9') break; } */ return 0; }
六、测试运行
1、空文件
2、一个字符的文件
3、只有一个词的文件
4、只有一行的文件
5、一个典型的源文件
6、自己乱打的文件:
七、项目小结
1、这次任务从无到有的,从不知如何开始到自己写算法统计复杂数据,过程虽然很无聊,改不好也容易烦躁,但是最后还是基本改完了,很是高兴
2、设置几个固定的文件来做 回归测试 还是远远不够的,这样你代码只会偏向于说:只要满足那几个文件就好了,但是要是文件一改动,代码运行结果马上出现统计问题。所以我在测试的时候,偏向于改变文件里的内容来做测试,改动的次数多了,就可以发现还有那些问题所在,上面的第6个图就是自己乱打的代码,其中通过这个方法就发现并解决的多行注释里可以包含空行的现象,因为注释行可以包含空行,所以 : 代码行!=总行 - 注释行 -空行,因为这样子会多减去一部分,导致代码行比实际少!
3、缺点:1)统计复杂数据的时候,最后一行不能是空行,空行统计会出错
2)统计复杂数据的时候,最后一行如果有注释,比如(printf();//我是注释,或者最后一行是 ' */ ' ),注释行将不能最后一行统计在内,也就是那一行仍然是属于代码行
3)我的算法是:只要有注释,该行就归为 注释行
4)也就发现最后一行这个问题,其他地方没有发现什么问题
优点:各种数据随便互相嵌套,统计结果基本都不会出现问题(除了上面说的),还有就是,实现了注释行里的可以空行可以计算进去和多行注释里面的代码不包含进代码行等等。
4、局限于现在只会c语言,其他语言比如java,学过一点,但是由于不熟练,就放弃了用java来写,还有就是递归目录没有实现。
5、这个项目所花的时间有点长(还好早一点写),大部分时间用在了分析复杂数据的算法,还有写完算法、优化算法的过程上。