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