个人项目:Word Count(c语言)

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 文件的代码行数、空行数、注释行数。

思考过程

一个功能一个模块,然后根据输入参数不同选择进行不同的功能模块
下面的遇到的困难及解决方法同时也是找资料的心路历程

遇到的困难及解决方法

  • 看不懂项目要求
    • 具体:一开始不清楚这个项目的要求是,点开做好的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的时候一定要耐心,做计算注释行那段我真的,差点暴走
  • 百度很重要!

猜你喜欢

转载自www.cnblogs.com/7AAAAAAA/p/12445639.html