github地址:https://github.com/7AAAAAAA/c-wc.exe
项目相关要求
实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。
具体功能要求:
程序处理用户需求的模式为:
wc.exe [parameter] [file_name]
- 基本功能列表:
- wc.exe -c file.c //返回文件 file.c 的字符数(实现)
- wc.exe -w file.c //返回文件 file.c 的词的数目(实现)
- wc.exe -l file.c //返回文件 file.c 的行数(实现)
- 扩展功能:
- wc.exe -s //递归处理目录下符合条件的文件(实现但待优化)
- wc.exe -a //返回更复杂的数据(代码行/空行/注释行)(实现)
空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”。
代码行:本行包括多于一个字符的代码。
注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释:
} //注释
在这种情况下,这一行属于注释行。
[file_name]: 文件或目录名,可以处理一般通配符。
- 高级功能:
- wc.exe -x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。(没实现)
需求举例: wc.exe -s -a .c返回当前目录及子目录中所有.c 文件的代码行数、空行数、注释行数。
- wc.exe -x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。(没实现)
思考过程
一个功能一个模块,然后根据输入参数不同选择进行不同的功能模块
下面的遇到的困难及解决方法同时也是找资料的心路历程
遇到的困难及解决方法
- 看不懂项目要求
- 具体:一开始不清楚这个项目的要求是,点开做好的exe,然后就来一个窗口,然后输入“-c file.c”等参数,然后给出结果,还是直接在cmd.exe上输入命令执行操作,就很纠结
- 解决方法:多看了几遍项目要求,然后因为我所知道的命令行程序里的仅有几个命令的样子也是 “ -某个字母 ”这个样子,然后就百度了这种命令是能不能自已做的(因为完全不了解),如果可以做,怎么输入文件名传参数什么的,后来发现main函数有个*argv[]可以传参,而且参数与参数之间要有空格,恍然大悟,这不就跟要求的程序处理用户需求的模式一毛一样,看懂了贼开心,终于可以开始做了
- 不知道c能不能使用正则
- 具体:因为在学习js,然后是js是可以使用正则的,但是在c里面我还没有用过,不知道c里面能不能用
- 解决方法:百度后找到了linux下可以使用的正则,然而我是window,然后好像是找到了window下可以使用的,结果一用,说是什么c++然后又是版本问题什么的,没结果,就决定试试不要用正则然后做出来
- 计算注释行/代码行/空行好乱
- 具体:不用正则计算真的有点儿乱啊
- 解决方法:用了四个标志变量来分别表示这一行的情况,比如标志这一行有几个可计入代码的可显示字符,flag为0,1,2分别表示0个,1个和超过1个,然后就是标志这一行是否已经被算为注释行flagcommit,防止出现/**///这些奇怪的代码然后计算两次等
- 文件递归遍历毫无头绪
- 具体:之前完全没想过c还能做这种事情,看到要求时候毫无头绪
- 解决方法:还是百度,各种百度c语言跟文件操作有关的东西,最后get到了文件信息存储数据结构_finddata_t,按某条件搜索文件_findfirst函数,由_findfirst提供的handle参数进行第二次搜素的_findnext函数
- 文件遍历中字符串的拼接问题
- 具体:strcat()函数要求第一个参数必须有足够大小可容纳第二个参数,而我的参数都是char*类型的,然后就出现了不是bug但函数进行不下的问题,起初不知道是什么问题,后来百度了strcat()才知道它这个性质的
- 解决方法:利用malloc手动开辟足够的空间,当然用完得释放
- cmd.exe中通配符*传参失败
- 具体:在cmd.exe传入后自己会执行搜索当前目录的文件,而不是传入字符,这导致无法获取到这个参数
- 解决方法:百度了好久:)没找到怎么解决,所以文件遍历就这个没搞定,只好暂时变成固定条件为*.c(获取目录以及子目录下的所有c文件)
关键代码及设计说明
计算字符数的函数chars
fgetc到一个字符就count++
int chars(char* name){
FILE *fp;
int ch;
int count = 0;
if((fp = fopen(name,"r")) == NULL ){
printf("file: %s\\n",name);
printf("错误:找不到该文件或者打不开该文件\\n");
count = -1;
}
else {
while((ch = fgetc(fp)) != EOF) {
count++;
}
}
fclose(fp);
if(count >= 0) {
printf("file: %s\\n char: %d\\n",name,count);
}
return 0;
}
计算词数的函数words
flag:当遇到字母时变成1,然后count++,然后当遇到非字母时就又变成0
int words(char* name){
FILE *fp;
int ch;
int flag = 0;
int count = 0;
if((fp = fopen(name,"r")) == NULL ){
printf("file: %s\\n",name);
printf("错误:找不到该文件或者打不开该文件\\n");
count = -1;
}
else {
while((ch = fgetc(fp)) != EOF) {
if((ch >= \'a\' && ch <= \'z\' || ch >= \'A\' && ch <= \'Z\') && flag == 0){
flag = 1;
}else if(((ch >= \'a\' && ch <= \'z\' || ch >= \'A\' && ch <= \'Z\') && flag == 1) || flag == 0){
continue;
}else if(flag == 1) {
count++;
flag = 0;
}
}
if(flag == 1) {
count++;
}
}
fclose(fp);
if(count >= 0) {
printf("file: %s\\n word: %d\\n",name,count);
}
return count;
}
计算行数的函数lines
fgetc到一个换行符就为一行
然后有一个flag标志来决定第一行算不算一行,如果没有任何字符,那么就不会count++
int lines(char* name){
FILE *fp;
int ch;
int count = 0;
int flag = 0;
if((fp = fopen(name,"r")) == NULL ){
printf("file: %s\\n",name);
printf("错误:找不到该文件或者打不开该文件\\n");
count = -1;
}
else {
while((ch = fgetc(fp)) != EOF) {
flag = 1;
if(ch == \'\\n\'){
count++;
}
}
if(flag == 1){
count++;
}
}
fclose(fp);
if(count >= 0) {
printf("file: %s\\n line: %d\\n",name,count);
}
return count;
}
计算空行数,代码行数和注释行数的函数mores(啊好长的)
利用四个标志
flag:可显示字符为0,1或者超过1
flagcommit:这一行是否已被加为注释行
flagcode:这一行是否已被加为代码行
flagchar:文件中是否有任何字符
来表示这一行的情况
具体想法非常简单,就是考虑多种情况,然后用标志来判断,代码好长
int mores(char* name){
FILE *fp;
int ch;
int count[3] = {0,0,0};
int flag = 0; //超过一个可显示字符才会为1
int flagcommit = 0;//这一行是否已被加为注释
int flagcode = 0;//这一行是否已被加为代码
int flagchar = 0;//文件中是否有任何字符
if((fp = fopen(name,"r")) == NULL ){
printf("file: %s\\n",name);
printf("错误:找不到该文件或者打不开该文件\\n");
count[0] = -1;
}
else {
while((ch = fgetc(fp)) != EOF) {
if(flagchar == 0) {
flagchar = 1;
}
if(ch == \'/\') { //遇到一个/
if((ch = fgetc(fp)) != EOF){
if(ch == \'/\') {
if(flag == 2 && flagcode == 0) {
count[1]++;
flagcode = 1;
}
if(flagcommit == 0 && flagcode == 0){
count[2]++;
flagcommit = 1;
}
while((ch = fgetc(fp)) != EOF) {
if(ch == \'\\n\') {
flag = 0;
flagcode = 0;
flagcommit = 0;
break;
}
}
}else if(ch == \'*\') {
if(flag == 2 && flagcode == 0) {
if(flagcommit == 1) {
count[2]--;
}
count[1]++;
flagcode = 1;
}
if(flagcommit == 0 && flagcode == 0){
count[2]++;
flagcommit = 1;
}
while((ch = fgetc(fp)) != EOF) {
if(ch == \'\\n\') {
count[2]++;
flagcommit = 0;
flagcode = 0;
flag = 0;
}else if(ch == \'*\') {
if((ch = fgetc(fp)) != EOF) {
if(ch == \'/\') {
flagcommit = 1;
break;
}
}
}
}
}
}
}else if(ch >\'\\x20\'){ //不知道后面什么情况,bug待定
if(flag == 0){
flag = 1;
}else if(flag == 1) {
flag = 2;
}
}else if(ch == \'\\n\') {
if((flag == 0 || flag == 1) && flagcode == 0 && flagcommit == 0){
count[0]++;
}else if(flag == 2 && flagcode == 0){
count[1]++;
}
flag = 0;
flagcommit = 0;
flagcode = 0;
}else {
continue;
}
}
}
if((flag == 0 || flag == 1 )&& flagcode == 0 && flagcommit == 0 && flagchar == 1){
count[0]++;
}else if(flag == 2 && flagcode == 0 && flagcommit == 0) {
count[1]++;
}else if(flag == 2 && flagcode == 0 && flagcommit == 1) {
count[1]++;
count[2]--;
}
fclose(fp);
if(count[0] >= 0) {
printf("file: %s\\n empty:%d\\t code: %d\\tnote: %d\\n",name,count[0],count[1],count[2]);
}
return 0;
}
除了-s外的其它命令的选择函数somecmdselect
根据参数不同选择不同操作
int somecmdselect(char* cmd,char* file) {
if(strcmp(cmd,"help") == 0) {
printf(" -c file.c 返回文件file.c的字符数\\n");
printf(" -w file.c 返回文件file.c的词的数目\\n");
printf(" -l file.c 返回文件file.c的行数\\n");
printf(" -a file.c 返回文件file.c的空行/代码行/注释行\\n");
printf(" -s a b 返回当前目录及子目录中符合b条件的a操作\\n");
printf(" a可为-c/-w/-l/-a\\n");
printf(" b可为*/*.c等(此处仅支持*.c)");
}else if(!file){
printf("缺少参数\\n");
printf("可通过 help参数查看可使用命令\\n");
}else if(strcmp(cmd,"-c") == 0) {
chars(file);
}else if(strcmp(cmd,"-w") == 0) {
words(file);
}else if(strcmp(cmd,"-l") == 0) {
lines(file);
}else if(strcmp(cmd,"-a") == 0) {
mores(file);
}else {
printf("\\\'%s\\\'命令不存在\\n",cmd);
printf("可通过 help参数查看可使用命令\\n");
}
return 0;
}
递归处理当前目录以及子目录的.c文件的函数watchfiles(待优化)
利用_findfirst函数和_findnext函数来搜索文件,当搜到文件夹就进入递归,搜到符合条件的文件就进入命令选择函数somecmdselect进行选择,然后执行操作
int watchfiles(char* folder,char* factor,char* argv[]){
struct _finddata_t f;
long handle;
char* currentfactor = (char*)malloc(sizeof(char*)*(strlen(folder)+strlen(factor)+2));//存放当前目录的factor
char* currentfolder= (char*)malloc(sizeof(char*)*(strlen(folder)+strlen(factor)+2));//当前目录
char* path = (char*)malloc(sizeof(char*)*(strlen(folder)+2));
strcpy(path,folder);
strcpy(currentfactor,strcat(path,"/"));
strcpy(currentfolder,path);
handle = _findfirst(strcat(currentfactor,factor),&f);
if(handle == -1L) {
return 0;
}else {
do{
if(f.attrib != _A_SUBDIR){
char* name = (char*)malloc(sizeof(char*)*(strlen(path)+strlen(f.name)));
strcpy(name,path);
somecmdselect(argv[2],strcat(name,f.name));
free(name);
}
}while(_findnext(handle,&f) == 0);
}
free(currentfactor);
_findclose(handle);
handle = _findfirst(strcat(currentfolder,"*"),&f);
if(handle == -1L) {
return 0;
}else {
do{
if(f.attrib == _A_SUBDIR && strcmp(f.name,".") && strcmp(f.name,"..")){
char* newpath = (char*)malloc(sizeof(char*)*(strlen(path)+strlen(f.name)));
strcpy(newpath,path);
watchfiles(strcat(newpath,f.name),factor,argv);
free(newpath);
}
}while(_findnext(handle,&f) == 0);
}
_findclose(handle);
free(currentfolder);
free(path);
return 0;
}
头文件
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <stdlib.h>
主函数
int main(int argc, char* argv[]){
if(argc > 1){
if(strcmp(argv[1],"-s") == 0){
if(argc > 3){
watchfiles(".","*.c",argv);
}else {
printf("缺少参数\\n");
printf("可通过 help参数查看可使用命令\\n");
}
}else {
somecmdselect(argv[1],argv[2]);
}
}else {
printf("缺少参数\\n");
printf("可通过 help参数查看可使用命令\\n");
}
return 0;
}
运行结果截图
help功能
chars(),words().lines()用的测试文件为
- 空文件
- 只有一个字符的文件
- 只有一个词的文件
- 只有一行的文件
- 一个典型的源文件
chars()
words()
lines()
mores()用到的测试文件,
- 空文件
- 只有一个字符的文件 同上
- 只有一个词的文件 同上
- 只有一行的文件
- 有这种注释/**/的文件
- 一个典型的源文件
扩展功能mores()
扩展功能watchfiles()
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 20 |
Development | 开发 | 1980 | 2010 |
· Analysis | · 需求分析 (包括学习新技术) | 600 | 400 |
· Design Spec | · 生成设计文档 | 120 | 100 |
· Design Review | · 设计复审 (和同事审核设计文档) | 60 | 20 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 30 |
· Design | · 具体设计 | 180 | 160 |
· Coding | · 具体编码 | 480 | 720 |
· Code Review | · 代码复审 | 120 | 100 |
· Test | · 测试(自我测试,修改代码,提交修改) | 360 | 480 |
Reporting | 报告 | 600 | 270 |
· Test Report | · 测试报告 | 240 | 180 |
· Size Measurement | · 计算工作量 | 120 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 240 | 60 |
合计 | 2610 | 2300 |
项目小结
- 对于c语言其实了解的不够多,比如c语言的正则,还有搜索文件_findfirst函数,这个我第一次听,之前都不知道c可以有这样的操作
- debug的时候一定要耐心,做计算注释行那段我真的,差点暴走
- 百度很重要!