1、Github项目地址

https://github.com/ShadowEvan/homework

  • 基本功能
    • -c 统计文件字符数(实现)
    • -w 统计文件词数(实现)
    • -l  统计文件行数(实现)
  • 扩展功能
    • -s 递归处理目录下符合条件的文件(实现)
    • -a 返回文件代码行/空行/注释行(实现)
    • 简单处理一般通配符(*, ?)(实现)
  • 高级功能
    • -x 支持图形界面(未实现)

 

2、PSP表格

PSP2.1

Personal Software Process Stages

预估耗时

(分钟)

实际耗时

(分钟)

Planning

计划

 20  30

· Estimate

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

 1900  2340

Development

开发

600  1200

· Analysis

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

 90  120

· Design Spec

· 生成设计文档

 30  45

· Design Review

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

 30  45

· Coding Standard

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

 20  20

· Design

· 具体设计

 40  60

· Coding

· 具体编码

 240  300

· Code Review

· 代码复审

 120  180

· Test

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

 120  180

Reporting

报告

 60  100

· Test Report

· 测试报告

 30  30

· Size Measurement

· 计算工作量

 20  30

· Postmortem & Process Improvement Plan

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

 30  30

合计

   1920 2370 

 

3、解题思路

  拿到题目后,觉得基本功能比较容易实现,扩展功能递归遍历文件有点困难,需要查阅相关资料。我的方法主要是,打开文件后每次获取一个字符并进行处理。

(1)返回文件的字符数  

思路:每获取一个非空字符及非转义字符如\n、\t则进行统计。

(2)返回文件的词数  

思路:每遇到一个字母或数字则标记为进入词内状态并进行统计,词内状态下遇到字母或数字不统计,遇到其他字符则标记为词外状态。

(3)返回文件的行数

思路:行数上仅记录有效行数,即不包括文本或代码的空行不计在内,方法类似于词数统计。

(4)递归处理目录下符合条件的文件

思路:先识别文件名是否含有通配符,要求输入的第一个参数为-s,满足条件则开始获取程序当前路径,并递归遍历查找与模式匹配的文件名,按其他参数处理符合条件的文件。

这个功能应该是耗时最长完成的,主要原因在于对于一些文件处理的库和函数不够熟悉,需要花大量时间在网上查找并筛选合适的资料。在这方面有个缺陷就是,所查找到的库是有系统依赖性的,这意味着无法做到跨平台,因此还有许多改进空间。

(5)返回更复杂的数据

思路:方法大体与(2)和(3)一致,利用状态来统计,主要的难点在于多行注释以及各种边界情况,为此我另外设置一个函数来处理多行注释的问题。以下是我在这个功能中对一些概念的界定:

若注释前本行有代码,本行算代码行。

若本行仅有 { 或 } ,本行算空行。

若多行注释中包括空行,该空行算注释行。


 

4、设计实现过程

 

int option(int argc, char *argv[], char *file_name);
int recurrence(int argc, char *argv[], char *file_name);
int file_travesal(const char *dir, int argc, char *argv[], char *file_name);
int wildchar_match(char *file_name, char *pattern, int ignore_case);
unsigned int char_count(FILE *fp);
unsigned int word_count(FILE *fp);
unsigned int line_count(FILE *fp);
void code_count(FILE *fp);
函数option 中包含char_count(-c)、word_count(-w)、line_count(-l)、code_count(-a)
函数recurrence封装了file_travesal函数,file_travesal中运用了wildchar_match函数和option函数

5、代码说明
 1 unsigned int char_count(FILE *fp)
 2 {
 3     unsigned int cc = 0;
 4     char ch;
 5 
 6     while ((ch = getc(fp)) != EOF) {
 7         if (ch != ' ' && ch != '\n' && ch != '\t') {
 8             cc++;
 9         }
10     }
11 
12     return cc;
13 }
字符统计
 1 unsigned int word_count(FILE *fp)
 2 {
 3     unsigned int wc = 0;
 4     char ch;
 5     bool state = _OUT;
 6 
 7     while ((ch = getc(fp)) != EOF) {
 8         if ((ch > 'z' || ch < 'a') && (ch > 'Z' || ch < 'A') && (ch > '9' || ch < '0')) {
 9             if (state == _IN) {
10                 state = _OUT;
11             }
12         } else if (state == _OUT) {
13             state = _IN;
14             wc++;
15         }
16     }
17 
18     return wc;
19 }
词数统计
 1 unsigned int line_count(FILE *fp)
 2 {
 3     unsigned int lc = 0;
 4     char ch;
 5     bool state = _OUT;
 6 
 7     while ((ch = getc(fp)) != EOF) {
 8         if (ch != ' ' && ch != '\t' && ch != '\n' && state == _OUT) {
 9             state = _IN;
10             lc++;
11         } else if ('\n' == ch) {
12             state = _OUT;
13         }
14     }
15 
16     return lc;
17 }
行数统计
 1 void code_count(FILE *fp)
 2 {
 3     unsigned int n_code = 0;
 4     unsigned int n_blank = 0;
 5     unsigned int n_comment = 0;
 6     char ch1, ch2;
 7     bool in_code = false;
 8     bool in_comment = false;
 9 
10     while ((ch1 = getc(fp)) != EOF) {
11         if (ch1 != ' ' && ch1 != '\t' && ch1 != '\n' && ch1 != '{' && ch1 != '}' && !in_comment && !in_code) {
12             if (ch1 == '/') {
13                 // Multi-line comment
14                 if ((ch2 = getc(fp)) == '*') {
15                     n_comment = in_multicomment(fp, n_comment);
16                 } else if (ch2 == '/') {
17                     in_comment = true;
18                     n_comment++;
19                 } else {
20                     in_code = true;
21                     n_code++;
22                 }
23             } else {
24                 in_code = true;
25                 n_code++;
26             }
27         // Reset the state and count blank lines for each line
28         } else if ('\n' == ch1) {
29             if (in_code) {
30                 in_code = false;
31             } else if (in_comment) {
32                 in_comment = false;
33             } else
34                 n_blank++;
35         }
36     }
37 
38     printf("Blank lines: %d\n", n_blank);
39     printf("Code lines: %d\n", n_code);
40     printf("Comment lines: %d\n", n_comment);
41 }

根据遇到的字符设置状态,/*则为多行注释,//为单行注释,其余非空行为代码行

每处理完一行则重置状态并统计空行

 

 1 /* Get current work directory and traverse recursively the directory */
 2 int recurrence(int argc, char *argv[], char *file_name)
 3 {
 4     char *buffer;
 5 
 6     if ((buffer = _getcwd(NULL, 0)) == NULL) {
 7         perror("_getcwd error");
 8         free(buffer);
 9         exit(-1);
10     }
11 
12     file_travesal(buffer, argc, argv, file_name);
13     free(buffer);
14 
15     return 0;
16 }
17 
18 int file_travesal(const char *dir, int argc, char *argv[], char *file_name)
19 {
20     struct _finddata_t fa;
21     long handle;
22     char new_dir[MAX_PATH];
23     strcpy(new_dir, dir);
24     strcat(new_dir, "\\*.*");
25 
26     if ((handle = _findfirst(new_dir, &fa)) == -1L) {
27         printf("Failed to find first file!\n");
28         return -1;
29     }
30 
31     do {
32         // Determine if it is a subdirectory
33         if (fa.attrib & _A_SUBDIR) {
34             if ((strcmp(fa.name, ".") != 0) && (strcmp(fa.name, "..") != 0)) {
35                 strcpy(new_dir, dir);
36                 strcat(new_dir, "\\");
37                 strcat(new_dir, fa.name);
38                 file_travesal(new_dir, argc, argv, file_name);
39             }
40         } else {
41             // Skip it when not match
42             if (!wildchar_match(fa.name, file_name, 1)) {
43                 continue;
44             }
45             strcpy(new_dir, dir);
46             strcat(new_dir, "\\");
47             strcat(new_dir, fa.name);
48             printf("%s:\n", fa.name);
49             option(argc, (argv + 1), new_dir);
50             printf("\n");
51         }
52     } while (_findnext(handle, &fa) == 0);
53     return 0;
54 }

获取程序当前工作目录,并递归遍历整个目录文件,若文件名与用户输入的模式匹配,则执行选项操作

 

 1 int option(int argc, char *argv[], char *file_name)
 2 {
 3     int c = 0;
 4     FILE *fp;
 5 
 6     if ((fp = fopen(file_name, "r")) == NULL) {
 7         printf("Error! There has been a problem opening or reading from the file.\n");
 8         exit(-1);
 9     }
10 
11     if (argc == 2) {
12         printf("The number of characters: %d\n", char_count(fp));
13         rewind(fp);
14         printf("The number of words: %d\n", word_count(fp));
15         rewind(fp);
16         printf("The number of lines: %d\n", line_count(fp));
17     }
18     
19     // Get the arguments and execute the relevant operation
20     while (--argc > 0 && (*++argv)[0] == '-') {
21         while (c = *++argv[0]) {
22             switch (c) {
23                 case 'c':
24                     printf("The number of characters in the file is %d\n", char_count(fp));
25                     rewind(fp);
26                     break;
27                 case 'w':
28                     printf("The number of words in the file is %d\n", word_count(fp));
29                     rewind(fp);
30                     break;
31                 case 'l':
32                     printf("The number of lines in the file is %d\n", line_count(fp));
33                     rewind(fp);
34                     break;
35                 case 'a':
36                     code_count(fp);
37                     rewind(fp);
38                     break;
39                 default:
40                     printf("Illegal option -%c\n", c);
41                     break;
42             }
43         }
44 
45         // recover argv for next use.
46         --argv[0];
47         --argv[0];
48     }
49     
50     fclose(fp);
51     return 0;
52 }

遍历所有参数,若参数的第一个字符为‘-’,则把其第二个字符赋值给c,并用switch结构进行选项操作。

为实现多选项执行,每完成一个选项将文件指针指回头部。

46-47行恢复参数,防止指针指向的内容发生变化并对文件遍历造成影响。


6、测试运行
  1、空文件

  2、只有一个字符的文件

  3、只有一个词的文件

  4、只有一行的文件

  5、一个典型的源文件

  6、扩展功能


7、实际花费时间(见开头表格)


8、项目小结
  1. 在这个项目中,首先是对编程基本功的巩固,这次实践能使我更熟练地应用编程语言开发功能。
  2. 更深刻地体会到模块化设计开发的好处,虽然自己仍有待提高,但在开发过程中采用这种方式可以大大减少高耦合出现的情况。
  3. 了解并学习了目录文件的处理,查阅资料的过程中学习到了很多新的知识。
  4. PSP表格有助于开发者正确地认识自己,此次开发我耗时过长,藉此发现了自己的许多不足之处,比如对于不熟悉的领域一方面不敢大胆尝试,另一方面又颇有点闭门造车,耽误了大量时间和精力。
  5. 程序上,对于各种特殊情况的处理和测试仍不够完善,还需要多多努力。