个人项目作业——wc.exe

一、Github项目地址

https://github.com/PIPIYing/wc

 

二、项目概况

项目描述

Word Count
1. 实现一个简单而完整的软件工具(源程序特征统计程序)。
2. 进行单元测试、回归测试、效能测试,在实现上述程序的过程中使用相关的工具。
3. 进行个人软件过程(PSP)的实践,逐步记录自己在每个软件工程环节花费的时间。

 

项目要求

wc.exe 是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有wc.exe 的功能,并加以扩充,给出某程序设计语言源文件的字符数、单词数和行数。

实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。

 

项目功能

具体功能要求

程序处理用户需求的模式为:wc.exe [parameter] [file_name]

 

基本功能

1. 支持-c参数:返回文件 file.c 的字符数(实现)

2. 支持-w参数:返回文件 file.c 的词的数目(实现)

3. 支持-l参数:返回文件 file.c 的行数(实现)

 

基本功能列表:

wc.exe -c file.c     //返回文件 file.c 的字符数

wc.exe -w file.c    //返回文件 file.c 的词的数目  

wc.exe -l file.c      //返回文件 file.c 的行数

 

拓展功能

1. 支持-s参数:递归处理目录下符合条件的文件(实现)

2. 支持-a参数:返回更复杂的数据(代码行 / 空行 / 注释行)(实现)

3. 支持*,?参数:可以处理一般通配符(实现)

 

空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”。

代码行:本行包括多于一个字符的代码。

注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释:

    } //注释
在这种情况下,这一行属于注释行。

[file_name]: 文件或目录名,可以处理一般通配符。

 

高级功能

1. 支持-x参数:程序显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息(未实现)

2. 支持-s、-a和一般通配符(*,?)参数:返回当前目录及子目录中所有*.c 文件的代码行数、空行数、注释行数(实现)

 

 -x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。

需求举例:
  wc.exe -s -a *.c


返回当前目录及子目录中所有*.c 文件的代码行数、空行数、注释行数。

 

三、解题思路

看完题目描述和项目要求,明确这是一个cmd下的命令行系统,通过在cmd界面输入命令行来执行相关功能。

由于本人还没有学过Java,所以本次项目采用了C语言来完成。其中主要用到了头文件stdio.h定义的关于文件的函数,再基于各个功能模块进行分析。

 

四、设计实现过程

通过分析功能,将每个功能写成一个接口,再通过main函数接收命令行来调用相关功能的接口,实现功能并在cmd打印相关信息。

 

基本接口如下:

int countChar(char path[N], char file[N]); 

功能:统计文件字符数

设计:把基本路径和文件名拼接,调用函数fopen_s读取文件

   1. 若文件不存在,打印信息

   2. 若文件存在,则统计字符数(不包括中文字)和中文字(包括中文字符,其中中文字和中文字符各占2个字符位),打印字符数和中文字数

 

int countWord(char path[N], char file[N]);

功能:统计文件单词数

设计:把基本路径和文件名拼接,调用函数fopen_s读取文件

   1. 若文件不存在,打印信息

   2. 若文件存在,则通过统计空格数来统计单词数,打印单词数

 

int countLine(char path[N], char file[N]);

功能:统计文件行数

设计:把基本路径和文件名拼接,调用函数fopen_s读取文件

   1. 若文件不存在,打印信息

   2. 若文件存在,则通过函数fgets按行读取字符的特性,统计行数,打印行数

 

int countElse(char path[N], char file[N]);

功能:统计文件代码行数、空行数和注释行数

设计:把基本路径和文件名拼接,调用函数fopen_s读取文件

   1. 若文件不存在,打印信息

   2. 若文件存在,则三种行数的判断规则如下:

    a.代码行:调用函数fgets按行读取字符,除去空格、换行、\t之外都计入字符数,最后字符数>1即为代码行

    b.空行:调用函数fgets按行读取字符,除去空格、换行、\t之外都计入字符数,最后字符数<1即为空行

    c.注释行:分为//和/* */。前者读取到两个’/’即为注释行,后者读取到开始符号/*和结束符号*/之间的行均为注释行

   3. 最后打印代码行数、空行数和注释行数

 

int searchFile(char path[N],char mode[N],int tag);

功能:递归查找符合条件的文件

设计:通过拼接文件的查找路径,根据需求,调用函数_findfirst获取文件句柄

   1. 若文件句柄=-1,则文件不存在

        2. 根据文件句柄遍历文件并打印符合需求的文件名;有子目录则返回该函数,进行递归操作

 

int splitPath(char path[N],char mode[N]);

功能:拆分在cmd下输入的完整路径

设计:检查最后一个’/’后是不是文件名或一般通配符,并标记

   1. 路径符合要求,返回基本路径和文件名或一般通配符

        2. 路径不符合要求,则打印错误信息

 

五、代码说明

统计字符数

 1 //统计字符数
 2 int countChar(char path[N], char file[N])
 3 {
 4     FILE* fp = NULL;
 5     errno_t err;
 6     //英文字符计数器和中文字符计数器
 7     int n = 0, nC = 0;
 8     int c[M];
 9     int i = 0;
10     char filePath[N] = { 0 };
11 
12     strcpy_s(filePath, path);
13     strcat_s(filePath, file);
14 
15     err = fopen_s(&fp, filePath, "r");
16     if (fp == NULL)
17     {
18         printf("该文件不存在。\n");
19         return -1;
20     }
21     else {
22         for (i = 0; i < M; i++) {
23             c[i] = fgetc(fp);
24             if (feof(fp))  break;
25             else {
26                 if (c[i] > 127) {
27                     //每个中文字和中文符号各占2个字符位
28                     c[i + 1] = fgetc(fp);
29                     i += 2;
30                     //统计中文字
31                     nC++;
32                 }
33                 else if (c[i] < 0)  
34                     break;
35                 //统计字符数(包括字母、数字、英文符号、空格、换行,不包括中文字符)
36                 else  n++;
37             }
38         }
39         n -= 1;
40         printf("字符数:%d\t中文字:%d\n", n,nC);
41     }
42 
43     fclose(fp);
44     return n;
45 }


统计单词数

 1 //统计单词数
 2 int countWord(char path[N], char file[N]) {
 3     FILE* fp = NULL;
 4     errno_t err;
 5     int c;
 6     //计数器
 7     int n = 0; 
 8     char filePath[N] = { 0 };
 9 
10     strcpy_s(filePath, path);
11     strcat_s(filePath, file);
12 
13     err = fopen_s(&fp, filePath, "r");
14     if (fp == NULL)
15     {
16         printf("该文件不存在。\n");
17         return -1;
18     }
19     do
20     {
21         c = fgetc(fp);
22         if (feof(fp))  break;
23         //通过空格来统计单词数
24         else if (c == ' ')  n++;
25     } while (1);
26 
27     printf("单词数:%d\n", n);
28 
29     return n;
30 }

 

统计行数

 1 //统计行数
 2 int countLine(char path[N], char file[N]) {
 3     FILE* fp = NULL;
 4     errno_t err;
 5     char b[N];
 6     //计数器
 7     int n = 0;  
 8     char filePath[N] = { 0 };
 9 
10     strcpy_s(filePath, path);
11     strcat_s(filePath, file);
12 
13     err = fopen_s(&fp, filePath, "r");
14     if (fp == NULL)
15     {
16         printf("该文件不存在。\n");
17         return -1;
18     }
19     do {
20         //fgets()函数按行读取字符,每读一次为一行,以此计数
21         if (fgets(b, N, fp))  n++;
22         else break;
23     } while (1);
24 
25     printf("行数:%d\n", n);
26 
27     return n;
28 }

 

统计代码行、空行和注释行

  1 //统计代码行、空行和注释行
  2 int countElse(char path[N], char file[N]) {
  3     FILE* fp = NULL;
  4     errno_t err;
  5     char b[N];
  6     //记录代码行数,记录空行数,记录注释行,记录数组长度,记录字符长度,记录两种注释行数(//和/* */),记录三种行的总数
  7     int codeLine = 0, blankLine = 0, commentLine = 0, length = 0, count_char = 0, countC1 = 0, countC2 = 0, sum = 0;
  8     int i, j;
  9     //tag是查找/* */注释符号的前半部分的标记,查找成功记为1,查找失败记为0
 10     int tag = 0;
 11     char filePath[N] = { 0 };
 12 
 13     strcpy_s(filePath, path);
 14     strcat_s(filePath, file);
 15 
 16     err = fopen_s(&fp, filePath, "r");
 17     if (fp == NULL)
 18     {
 19         printf("该文件不存在。\n");
 20         return -1;
 21     }
 22     do {
 23         //fgets()函数按行读取字符
 24         if (fgets(b, N, fp)) {
 25             length = strlen(b);
 26             for (i = 0, count_char = 0; i < length; i++) {
 27                 if (b[i] != ' ' && b[i] != '\n' && b[i] != '\t')
 28                     //在一行中,除去空格、换行、\t之外都计入字符数
 29                     count_char++;
 30                 if (b[i] == '/' && b[i + 1] == '/') {
 31                     //第一种注释情况,读入的一行为注释行,跳出循环
 32                     countC1++;
 33                     //注释行不属于代码行
 34                     if (count_char > 1)  codeLine--;
 35                     //注释行不属于空行
 36                     else  blankLine--;
 37                     break;
 38                 }
 39                 if (tag == 1) {
 40                     countC2++;
 41                     //在该行查找注释的结束符号
 42                     for (j = i; j < length; j++) {
 43                         if (b[j] == '*' && b[j + 1] == '/') {
 44                             //查找结束符号成功,该行为注释行
 45                             //注释行不属于代码行
 46                             if (count_char > 1)  codeLine--;
 47                             //注释行不属于空行
 48                             else  blankLine--;
 49                             //现查找注释行前半部分失败
 50                             tag = 0;
 51                             break;
 52                         }
 53                     }
 54                     //tag=0说明该行是完整的注释行,同行查找结束符号成功
 55                     if (tag == 0)  break;
 56                     //否则同行查找结束符号失败,把tag置为1
 57                     else {
 58                         //注释行不属于代码行
 59                         if (count_char > 1)  codeLine--;
 60                         //注释行不属于空行
 61                         else  blankLine--;
 62                         break;
 63                     }
 64                 }
 65                 if (b[i] == '/' && b[i + 1] == '*' && tag == 0) {
 66                     countC2++;
 67                     tag = 1;
 68                     //第二种注释情况,读入的一行为注释行,同行查找结束符号
 69                     for (j = i + 2; j < length - 2; j++) {
 70                         if (b[j] == '*' && b[j + 1] == '/') {
 71                             //查找结束符号成功,该行为注释行
 72                             countC2++;
 73                             //注释行不属于代码行
 74                             if (count_char > 1)  codeLine--;
 75                             //注释行不属于空行
 76                             else  blankLine--;
 77                             //现查找注释行前半部分失败
 78                             tag = 0;
 79                             break;
 80                         }
 81                     }
 82                     //tag=0说明该行是完整的注释行,同行查找结束符号成功
 83                     if (tag == 0)  break;
 84                     //否则同行查找结束符号失败,把tag置为1
 85                     else {
 86                         //注释行不属于代码行
 87                         if (count_char > 1)  codeLine--;
 88                         //注释行不属于空行
 89                         else  blankLine--;
 90                         break;
 91                     }
 92                 }
 93             }
 94             //字符数大于1为代码行,否则为空行
 95             if (count_char > 1)  codeLine++;
 96             else  blankLine++;
 97         }
 98         else break;
 99     } while (1);
100 
101     commentLine = countC1 + countC2;
102     sum = codeLine + blankLine + commentLine;
103 
104     printf("代码行:%d\t", codeLine);
105     printf("空白行:%d\t", blankLine);
106     printf("注释行:%d\n", commentLine);
107 
108     //返回注释行,作为单元测试的内容
109     return commentLine;
110 }

 

递归查找符合条件的文件

  1 //递归查找符合条件的文件
  2 int searchFile(char path[N],char mode[N],int tag) {
  3     //文件句柄
  4     intptr_t Handle1;
  5     intptr_t Handle2;
  6     //文件结构体
  7     struct _finddata_t fileInfo1;
  8     struct _finddata_t fileInfo2;
  9     //特定文件查找路径、所有文件的查找路径、递归路径
 10     char nowPath_file[N] = { 0 };
 11     char nowPath_folder[N] = { 0 };
 12     char nowPath_re[N] = { 0 };
 13     //查找文件和文件夹的通识符
 14     char mode_N[N] = { "*.*" };
 15     int i, num = 0;
 16     //标记文件夹
 17     int mark = 0;
 18 
 19     //基本路径
 20     strcpy_s(nowPath_file, path);
 21     strcpy_s(nowPath_folder, path);
 22     strcpy_s(nowPath_re, path);
 23     //拼接特定文件和所有文件的查找路径
 24     strcat_s(nowPath_file, mode);
 25     strcat_s(nowPath_folder, mode_N);
 26 
 27     //根目录的文件句柄
 28     Handle1 = _findfirst(nowPath_folder, &fileInfo1);
 29     //.c类型文件的文件句柄
 30     Handle2 = _findfirst(nowPath_file, &fileInfo2);
 31 
 32     if ((Handle2 = _findfirst(nowPath_file, &fileInfo2)) == -1L)
 33         printf("该目录中没有这种类型的文件。\n");
 34     else {
 35         //查找文件后缀名.
 36         for (i = 0; i < strlen(fileInfo2.name); i++) {
 37             if (fileInfo2.name[i] == '.') {
 38                 mark = 1;
 39                 break;
 40             }
 41         }
 42         //只打印文件名,不打印文件夹名
 43         if (mark == 1 && strcmp(fileInfo2.name, ".") != 0 && strcmp(fileInfo2.name, "..") != 0) {
 44             printf("文件名:%s\n", fileInfo2.name);
 45             //单元测试的参数
 46             num = num + 1;
 47             //实现指令的功能
 48             if (tag == 1) {
 49                 countChar(nowPath_re, fileInfo2.name);
 50             }
 51             else if (tag == 2) {
 52                 countWord(nowPath_re, fileInfo2.name);
 53             }
 54             else if (tag == 3) {
 55                 countLine(nowPath_re, fileInfo2.name);
 56             }
 57             else if (tag == 4) {
 58                 countElse(nowPath_re, fileInfo2.name);
 59             }
 60             else;
 61             mark = 0;
 62         }
 63         //_findnext是以_findfirst为开始接着查找以下符合H2(.c)的文件并打印
 64         while (_findnext(Handle2, &fileInfo2) == 0) {
 65             //查找文件后缀名.
 66             for (i = 0; i < strlen(fileInfo2.name); i++) {
 67                 if (fileInfo2.name[i] == '.') {
 68                     //标记文件夹
 69                     mark = 1;
 70                     break;
 71                 }
 72             }
 73             //只打印文件名,不打印文件夹名
 74             if (mark == 1 && strcmp(fileInfo2.name, ".") != 0 && strcmp(fileInfo2.name, "..") != 0) {
 75                 printf("文件名:%s\n", fileInfo2.name);
 76                 //单元测试参数
 77                 num = num + 1;
 78                 //实现指令的功能
 79                 if (tag == 1) {
 80                     countChar(nowPath_re, fileInfo2.name);
 81                 }
 82                 else if (tag == 2) {
 83                     countWord(nowPath_re, fileInfo2.name);
 84                 }
 85                 else if (tag == 3) {
 86                     countLine(nowPath_re, fileInfo2.name);
 87                 }
 88                 else if (tag == 4) {
 89                     countElse(nowPath_re, fileInfo2.name);
 90                 }
 91                 else;
 92             }
 93             mark = 0;
 94         }
 95         _findclose(Handle2);
 96     }
 97 
 98     if ((Handle1 = _findfirst(nowPath_folder, &fileInfo1)) == -1L)
 99         printf("该目录中没有文件。\n");
100     else {
101         do {
102             if (fileInfo1.attrib & _A_SUBDIR) {
103                 //判断是否为"."当前目录,".."上一层目录,查找子目录的文件
104                 if ((strcmp(fileInfo1.name, ".") != 0) && (strcmp(fileInfo1.name, "..") != 0))
105                 {
106                     printf("\n%s文件夹\n", fileInfo1.name);
107                     //拼接子目录的路径,进行递归查找
108                     strcat_s(nowPath_re, fileInfo1.name);
109                     strcat_s(nowPath_re, "\\");
110                     //单元测试参数
111                     num += searchFile(nowPath_re, mode, tag);
112                 }
113             }
114           //循环该目录中所有文件
115         } while (_findnext(Handle1, &fileInfo1) == 0);  
116         _findclose(Handle1);
117     }    
118     //返回单元测试参数
119     return num;
120 }

 

拆分在cmd下输入的完整路径

 1 //拆分在cmd下输入的完整路径
 2 int splitPath(char path[N],char mode[N]) {
 3     int i, j, k, len;
 4     //1表示到符合输入要求的路径
 5     int mark = 0;
 6     len = strlen(path);
 7 
 8     for (i = len; i >= 0; i--) {
 9         if (path[i] == '\\') {
10             for (j = i; j < len; j++) {
11                 //是文件/通配符的标志
12                 if (path[j] == '.' || path[j] == '*') {
13                     for (k = 0; i < len; i++, k++) {
14                         mode[k] = path[i + 1];
15                         path[i + 1] = '\0';
16                     }
17                     mark = 1;
18                 }
19                 if (mark == 1)  break;
20             }
21             if (mark == 1)  break;
22         }
23     }
24     if (mark == 1)  return 1;
25     else  printf("输入文件路径有误,请重新输入!\n例:E:\\test\\file.c\n");
26     return -1;
27 }

 

主函数

 

 1 //主函数
 2 int main(int argc, char* argv[]) {
 3     //char path[N] = { "E:\\vs project\\wcTest\\" };
 4     //char mode[N] = { "*.*" };
 5     //char file[N] = { "test.c" };
 6     char path[N] = { "E:\\test" };
 7     char mode[N] = { 0 };
 8     //1是-c,2是-w,3是-l,4是-a
 9     int tag = 0;
10 
11     //统计
12     if (strcmp(argv[1], "-c") == 0) {
13         strcpy_s(path, argv[2]);
14         if (splitPath(path, mode) == 1)  countChar(path, mode);
15     }
16     else if (strcmp(argv[1], "-w") == 0) {
17         strcpy_s(path, argv[2]);
18         if (splitPath(path, mode) == 1)  countWord(path, mode);
19     }
20     else if (strcmp(argv[1], "-l") == 0) {
21         strcpy_s(path, argv[2]);
22         if (splitPath(path, mode) == 1)  countLine(path, mode);
23     }
24     else if (strcmp(argv[1], "-a") == 0) {
25         strcpy_s(path, argv[2]);
26         if (splitPath(path, mode) == 1)  countElse(path, mode);
27     }
28     //拓展功能
29     else if (strcmp(argv[1], "-s") == 0 && strcmp(argv[2], "-c") == 0) {
30         tag = 1;
31         strcpy_s(path, argv[3]);
32         if (splitPath(path, mode) == 1)  searchFile(path, mode, tag);
33     }
34     else if (strcmp(argv[1], "-s") == 0 && strcmp(argv[2], "-w") == 0) {
35         tag = 2;
36         strcpy_s(path, argv[3]);
37         if (splitPath(path, mode) == 1)  searchFile(path, mode, tag);
38     }
39     else if (strcmp(argv[1], "-s") == 0 && strcmp(argv[2], "-l") == 0) {
40         tag = 3;
41         strcpy_s(path, argv[3]);
42         if (splitPath(path, mode) == 1)  searchFile(path, mode, tag);
43     }
44     else if (strcmp(argv[1], "-s") == 0 && strcmp(argv[2], "-a") == 0) {
45         tag = 4;
46         strcpy_s(path, argv[3]);
47         if (splitPath(path, mode) == 1)  searchFile(path, mode, tag);
48     }
49     //批处理同类型文件
50     else if (strcmp(argv[1], "-s") == 0) {
51         strcpy_s(path, argv[2]);
52         if (splitPath(path, mode) == 1)  searchFile(path, mode, tag);
53     }
54     else printf("指令有误,请重新输入!\n");
55 
56     system("pause");
57 
58     return 0;
59 }

 

 

六、测试运行

测试运行分为三个模块:单元测试、回归测试、运行测试

1. 单元测试

单元测试分为三部分测试:

TestMethod1:统计字符数、单词数、行数、其他行数的测试,通过返回值确定函数是否正确

TestMethod2:递归文件的测试,通过目录(包括子目录)中的文件个数确定函数是否正确

TestMethod3:拆分路径的测试,通过返回值确定函数是否正确

测试结果如下图:

 

小结:单元测试通过,编写的各个接口可用

 

2. 回归测试

回归测试分为五部分测试,如下所示。

空文件测试:

只有一个字符的文件测试:

 

 只有一个词的文件测试:

 

 只有一行的文件测试:

 

 一个典型的源文件测试:

 

 

小结:回归测试通过

 

3. 运行测试

运行需求中的各条命令行,并展示运行测试结果如下。

基本功能

wc.exe –c E:\test\file.c

 

wc.exe –w E:\test\file.c

 

wc.exe –l E:\test\file.c

 

 

拓展功能

测试文件夹如图所示

        

wc.exe –s E:\test\*.c

 

wc.exe –a E:\test\file.c

 

wc.exe –s E:\test\*

 

wc.exe –s E:\test\file?.c

 

 

高级功能

测试文件夹如图所示

         

wc.exe –s –a E:\test\*.c

 

wc.exe –s –a E:\test\*

 

七、PSP表格

PSP2.1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

Planning

计划

   

· Estimate

· 估计这个任务需要多少时间

300

360

Development

开发

   

· Analysis

· 需求分析 (包括学习新技术)

10

30

· Design Spec

· 生成设计文档

10

20

· Design Review

· 设计复审 (和同事审核设计文档)

10

10

· Coding Standard

· 代码规范 (为目前的开发制定合适的规范)

5

5

· Design

· 具体设计

30

40

· Coding

· 具体编码

180

240

· Code Review

· 代码复审

60

50

· Test

· 测试(自我测试,修改代码,提交修改)

120

90

Reporting

报告

   

· Test Report

· 测试报告

60

40

· Size Measurement

· 计算工作量

20

20

· Postmortem & Process Improvement Plan

· 事后总结, 并提出过程改进计划

30

15

 

合计

535

560

 

八、项目小结

1. 本次项目采用C语言完成,编码过程较为复杂,同时也学习到了C语言的一些补充知识。

2. 结合PSP表格可以认识到自己在预想和实际开发中的不足之处。

3. 要学习更多的语言来便捷地进行项目的开发

 

九、学习进度条

第N周

新增代码(行)

累计代码(行)

本周学习耗时(小时)

累计学习耗时(小时)

重要成长

2

451

451

9

9

了解了c语言头文件stdio.h中的其他函数



posted @ 2020-03-15 21:33  皮皮莹  阅读(266)  评论(0编辑  收藏  举报