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