个人项目作业

GitHub链接

 


 

 

项目相关要求

wc.exe 是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有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 的行数

 

扩展功能:
    -s   递归处理目录下符合条件的文件。
    -a   返回更复杂的数据(代码行 / 空行 / 注释行)。

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

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

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

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

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

高级功能:

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

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


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

 

PSP

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划  15   15 
· Estimate · 估计这个任务需要多少时间 15  15 
Development 开发  755  840
· Analysis · 需求分析 (包括学习新技术) 60 60
· Design Spec · 生成设计文档 20 25
· Design Review · 设计复审 (和同事审核设计文档) 10 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 15 15
· Design · 具体设计 60 75
· Coding · 具体编码 450 500
· Code Review · 代码复审 60 65
· Test · 测试(自我测试,修改代码,提交修改) 80 90
Reporting 报告  85  95
· Test Report · 测试报告 50 60
· Size Measurement · 计算工作量 15 15
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20 20
  合计   855  950

 

 

解题思路

基本功能:

-c:文件长度为字符数,当读取到回车时不计数。

-w:读取字符,当读取到字母或数字时,单词数加一。读取到其他字符时,单词数不变。

-l:读取一整行,读取的次数为行数。

扩展功能:

-s:根据输入参数的不同,调用其他不同功能的函数。根据路径获取文件时要判断该文件是否是文件夹,如果是文件夹要进入子目录递归继续查找。如果输入的文件名包含通配符,则要获得确定的文件名后再输出。

-a:读取一整行后,判断行内的字符成分,根据不同情况分别归类为空行、代码行和注释行。

 

设计实现过程

编程语言的选择:C

需要用到的新知识:文件操作函数,文件查找函数等一些完成项目需要用到的知识。

结构分析:每个功能设计为一个函数,根据用户输入参数不同调用不同函数。由于文件内容形式情况可能多样,因此写功能函数时需要判断不同情况。

遇到的困难

  一个主要困难是自己学的知识不足以完成项目,需要一边学习一边查阅一边打码。

  在做-a功能时由于每行内容和形式可能出现的情况比较多样,判断不同情况花了比较多时间。

  另外做-s功能时发现不会实现进入子目录和有通配符的情况,和同学讨论后改变了传输路径的形式,解决了这个问题。

 

代码说明

1. -c:计算文件字符数。

  用feof函数判断文件是否读取结束,fgetc函数读取字符并判断计数。

 1 int charactercount(char *path,char *filename) {//统计字符数
 2     FILE* fp;
 3     errno_t err;
 4     int c = 0;
 5     char ch;
 6     char way[100] = { '\0' };
 7     strcpy_s(way, path);
 8     strcat_s(way, 100, filename);
 9     err = fopen_s(&fp, way, "r");
10     printf("路径:%s\n", way);
11     if (NULL == fp) {
12         printf("文件为空或不存在。\n");
13         return -1;
14     }
15     else {
16         while (feof(fp) == 0) {
17             ch = fgetc(fp);
18             if (ch != '\0' && ch != '\n')
19                 c++;
20         }
21         c--;//使用feof函数读取字符要多读一次才能判断是否结束,因此要减1
22         fclose(fp);
23         return c;
24     }
25 }

2. -w:计算文件单词数

  同样运用feof和fgetc函数。遇到第一个字母或数字字符时单词数加一并标记,紧接着遇到字母或数字时单词数不变。当遇到非字母或数字字符时重置标记。

 1 int wordcount(char*path,char* filename) {//统计词数
 2     FILE* fp;
 3     errno_t err;
 4     int w = 0;
 5     char ch;
 6     bool mark = true;
 7     char way[100] = { '\0' };
 8     strcpy_s(way, path);
 9     strcat_s(way, 100, filename);
10     err = fopen_s(&fp, way, "r");
11     printf("路径:%s\n", way);
12     if (NULL == fp) {
13         printf("文件为空或不存在。\n");
14         return -1;
15     }
16     else {
17         while (feof(fp) == 0) {
18             ch = fgetc(fp);
19             if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')|| ch >= '0' && ch <= '9') {
20                 if (mark == true) {//当该字符为字母或数字且标记为true时,单词数加一
21                     w++;
22                     mark = false;
23                 }
24             }
25             else mark = true;//当该字符为其他字符时重置标志
26         }
27         fclose(fp);
28         return w;
29     }
30 }

3. -l:计算文件行数

  同样运用feof函数。fgets函数一次读取一整行,读取次数即为文件总行数。

 1 int linecount1(char*path,char* filename) {//统计行数
 2     FILE* fp;
 3     errno_t err;
 4     int l = 0;
 5     char str[100] = { 0 };
 6     char way[100] = { '\0' };
 7     strcpy_s(way, path);
 8     strcat_s(way, 100, filename);
 9     err = fopen_s(&fp, way, "r");
10     printf("路径:%s\n", way);
11     if (NULL == fp) {
12         printf("文件为空或不存在。\n");
13         return -1;
14     }
15     else {
16         while (feof(fp) == 0) {
17             fgets(str, sizeof(str) - 1, fp);//读取一整行
18             l++;
19         }
20         fclose(fp);
21         return l;
22     }
23 }

4. -a:计算文件空行、代码行和注释行

  同样运用feof和fgets函数。根据可能出现的不同情况来判断并归类空行、代码行和注释行。

 1 int linecount2(char*path,char* filename) {//统计空行、代码行和注释行
 2     FILE* fp;
 3     errno_t err;
 4     int k = 0, d = 0, z = 0, i;
 5     bool flag, tag = true;
 6     char str[100] = { '\0' };
 7     char way[100] = { '\0' };
 8     strcpy_s(way, path);
 9     strcat_s(way, 100, filename);
10     err = fopen_s(&fp, way, "r");
11     printf("路径:%s\n", way);
12     if (NULL == fp) {
13         printf("文件为空或不存在。\n");
14         return -1;
15     }
16     else {
17         while (feof(fp) == 0) {
18             fgets(str, sizeof(str) - 1, fp);
19             flag = true;
20             if (str[0] == '\n' || str[0] == '\0') {
21                 k++;//空行,无内容的情况
22             }
23             else if (str[0] == '{' || str[0] == '}') {
24                 if (str[1] == '\0' || str[1] == '\n')
25                     k++;//空行,只有大括号的情况
26                 else if (str[1] == '/' && str[2] == '/')
27                     z++;//注释行,括号后跟注释的情况
28                 else d++;
29             }
30             else if (str[0] == '/' && str[1] == '/') {
31                 z++;//用“//”符号的注释行
32             }
33             else if (str[0] == '/' && str[1] == '*') {
34                 z++;//用“/*”符号的多行注释
35                 i = 2;
36                 while (str[i] != '\n' && str[i] != '*' && str[i + 1] != '/')
37                     i++;
38                 if (str[i] == '\n')tag = false;//标记
39             }
40             else {
41                 if (tag == true) {//不是注释行
42                     i = 0;
43                     while (str[i] != '\n' && str[i] != '\0') {
44                         if (str[i] == '/' && str[i + 1] == '*') {
45                             z++;//多行注释的开始
46                             tag = false;
47                             break;
48                         }
49                         else if (str[i] != '\t' && str[i] != ' ') {
50                             d++;//代码前可能有位置控制符号
51                             flag = false;
52                             break;
53                         }
54                         else i++;
55                     }
56                     if (flag == true && tag == true) k++;
57                 }
58                 else {//注释行
59                     z++;
60                     i = 0;
61                     while (str[i] != '\n' && str[i] != '\0') {
62                         if (str[i] == '*' && str[i + 1] == '/')
63                             tag = true;//注释行结束,重置标记
64                         i++;
65                     }
66                 }
67             }
68         }
69         fclose(fp);
70     }
71     printf("%s文件中:\n", filename);
72     printf("文件中的空行数为%d。\n", k);
73     printf("文件中的代码行数为%d。\n", d);
74     printf("文件中的注释行数为%d。\n\n", z);
75     return k * k + d * d + z * z;//用于检测结果是否正确
76 }

5. -s:递归处理目录下符合条件的文件(支持通配符)

  用户输入路径,文件名和操作参数,通过拼接路径和文件名来查找符合条件的文件,并进行相应操作。Handle1查找目录下所有文件,并判断是否是文件夹,如果是拼接路径和文件名进入子目录。Handle2查找特定文件名的文件。

 1 int searchfile(char *path, char *op, char *mode) {//递归处理文件
 2     struct _finddata_t file1;
 3     struct _finddata_t file2;
 4     intptr_t Handle1;
 5     intptr_t Handle2;
 6     int c, w, l, a, s = 0;
 7     char way1[100] = { '\0' };
 8     char way2[100] = { '\0' };
 9     char way3[100] = { '\0' };
10     char way4[100] = { '\0' };
11     strcpy_s(way1, path);
12     strcpy_s(way2, path);
13     strcpy_s(way3, path);
14     strcpy_s(way4, path);
15     strcat_s(way1, "*");
16     strcat_s(way2, mode);
17 
18     if ((Handle1 = _findfirst(way1, &file1)) == -1L) {//Handle1查找所有文件
19         printf("%s中没有找到符合文件。\n",way4);
20     }
21     else {
22         do {
23             if (file1.attrib & _A_SUBDIR) {//该文件是文件夹
24                 if ((strcmp(file1.name, ".") != 0) && (strcmp(file1.name, "..") != 0)) {
25                     strcat_s(way4, file1.name);
26                     strcat_s(way4, "\\");
27                     s += searchfile(way4, op, mode);
28                 }//拼接地址,递归继续查找
29             }
30         }while(_findnext(Handle1, &file1) == 0);
31         _findclose(Handle1);
32     }
33 
34     if ((Handle2 = _findfirst(way2, &file2)) == -1L) {//Handle2查找特定文件
35         printf("%s中没有找到符合文件。\n",way3);
36     }
37     else {
38         do {
39             if (file2.attrib & _A_SUBDIR)continue;//文件夹不能查询
40             else if ((strcmp(file2.name, ".") == 0) || (strcmp(file2.name, "..") == 0))continue;
41             
42             if (strcmp(op, "-c") == 0) {
43                 c = charactercount(way3, file2.name);
44                 if (c != -1)printf("%s文件中共有%d个字符。\n\n", file2.name, c);
45                 s += c;
46             }
47             else if (strcmp(op, "-w") == 0) {
48                 w = wordcount(way3, file2.name);
49                 if (w != -1)printf("%s文件中共有%d个单词。\n\n", file2.name, w);
50                 s += w;
51             }
52             else if (strcmp(op, "-l") == 0) {
53                 l = linecount1(way3, file2.name);
54                 if (l != -1)printf("%s文件中共有%d行。\n\n", file2.name, l);
55                 s += l;
56             }
57             else if (strcmp(op, "-a") == 0) {
58                 a = linecount2(way3, file2.name);
59                 s += a;
60             }
61             else {
62                 printf("输入操作错误。\n");
63                 s += -1;
64             }
65         } while (_findnext(Handle2, &file2) == 0);
66         _findclose(Handle2);
67     }
68     return s;
69 }

6.主函数

 1 void main(int argc, char* argv[]) {
 2     int c = 0, w = 0, l = 0, a = 0, s = 0;
 3     int len = 0, i = 0, j = 0;
 4     char path[50] = { '\0' };
 5     char mode[50] = { '\0' };
 6     char str[100] = { '\0' };
 7     if (strcmp(argv[1], "-c") == 0) {
 8         c = charactercount(path,argv[2]);
 9         printf("文件中的字符数为%d。\n", c);
10     }
11     else if (strcmp(argv[1], "-w") == 0) {
12         w = wordcount(path,argv[2]);
13         printf("文件中的单词数为%d。\n", w);
14     }
15     else if (strcmp(argv[1], "-l") == 0) {
16         l = linecount1(path,argv[2]);
17         printf("文件中的行数为%d。\n", l);
18     }
19     else if (strcmp(argv[1], "-a") == 0) {
20         a = linecount2(path,argv[2]);
21         if (a == 0)printf("文件为空。\n");
22     }
23     else if (strcmp(argv[1], "-s") == 0) {
24         len = strlen(argv[3]) - 1;
25         i = len;
26         strcpy_s(str, argv[3]);
27         while (i >= 0) {
28             if (str[i] == '\\')break;
29             else i--;
30         }
31         if (i >= 0) {
32             strncpy_s(path, str, i + 1);
33             while (i <= len) {
34                 mode[j] = str[i];
35                 j++; i++;
36             }//拆分路径
37         }
38         else strcpy_s(mode, argv[3]);
39         s = searchfile(path, argv[2], mode);
40     }
41     else printf("输入错误。\n");
42 }

 

测试运行

  程序实现了-c,-w,-l,-s,-a功能,同时-s输入支持通配符。

1.测试文件

    

  

 2.单个文件-c功能

 

 3.单个文件-w功能

 

 4.单个文件-l功能

 

 5.单个文件-a功能

 

 6.-s包含通配符的几种情况

①输入特定文件名

 

②输入*.c

 

 

 ③输入*

 

 ④输入fil?.c

 

 

项目小结

1.这次个人项目是一次非常重要的经历,短时间内快速学习新知识然后立刻做一个程序出来,这是从来没有过的体验,在这过程中我也积累下了很多经验。

2.由于是首次比较正式的开发项目且,我在完成项目的过程中也走了不少弯路,主要问题还是掌握的知识不够。同时我也意识到事先规划好大概的时间和流程是很重要的,这才能保证自己的项目能按期完成。

 

posted @ 2020-03-15 19:24  Sheaxx  阅读(188)  评论(0编辑  收藏  举报