GitHub:https://github.com/SInXW/wc.exe
一、题目描述
1.题目描述
1. 实现一个简单而完整的软件工具(源程序特征统计程序)。
2. 进行单元测试、回归测试、效能测试,在实现上述程序的过程中使用相关的工具。
3. 进行个人软件过程(PSP)的实践,逐步记录自己在每个软件工程环节花费的时间。
2.WC 项目要求
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]: 文件或目录名,可以处理一般通配符。
二、解题思路
检测用户输入指令,再根据指令执行相应操作
统计字符操作:依次读取字符,统计字符数
统计词操作:读取连续英文字符,若中断则词数加一
统计行数操作:遇换行符则行数加一
三、关键代码
统计字符操作:依次读取字符,统计字符数
//统计字符数 int countCharacter(FILE* file) { int character = 0, chNum = 0; while (!feof(file)) { character = fgetc(file); //换行符不计 if (character == '\n') { continue; } chNum++; } if (chNum == 0) { return 0; } return --chNum; }
统计行数操作:遇换行符则行数加一
//统计行数 int countLine(FILE* file) { int lineNum = 0, word = 0; int flag = 0; //标识是否为空文件,空文件0行 if (file != NULL) { while (!feof(file)) { word = fgetc(file); //遇换行符 if (word == '\n') { lineNum++; } } return lineNum; } }
统计词操作:读取连续英文字符,若中断则词数加一
//统计词数 int countWord(FILE* file) { int character = 0, word = 0, isWord = 0; while (!feof(file)) { character = fgetc(file); if ((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z')) { if (isWord == 0) { isWord = 1; } } else { if (isWord == 1) { word++; isWord = 0; } } } return word; }
扩展功能:
方式:利用结构体_finddata_t(由#include <io.h>引入),获取文件信息
思路:对每一个文件判断是否为文件夹,若为文件夹,则递归本操作;否则针对该文件执行基础操作(统计行、词、字符);
//递归处理目录下文件 /* _finddata_t结构体说明 struct _finddata_t{ unsigned attrib; time_t time_create; time_t time_access; time_t time_write; _fsize_t size; char name[_MAX_FNAME]; }; unsigned atrrib:文件属性的存储位置。文件属性是用位表示的,主要有以下一些: _A_ARCH(存档)、 _A_HIDDEN(隐藏)、_A_NORMAL(正常)、_A_RDONLY(只读)、 _A_SUBDIR(文件夹)、_A_SYSTEM(系统) char name[_MAX_FNAME]:文件的文件名。这里的_MAX_FNAME是一个常量宏,它在头 文件中被定义,表示的是文件名的最大长度。 针对_finddata_t结构体的主要函数: long _findfirst( char *filespec, struct _finddata_t *fileinfo ); 返回值:如果查找成功的话,将返回一个long型的唯一的查找用的句柄。这个句柄将会在_findnext函数中被使用。失败返回-1. 参数: filespec:标明文件的字符串,可支持通配符。 fileinfo :这里就是用来存放文件信息的结构体的指针。 int _findnext( long handle, struct _finddata_t *fileinfo ); 返回值:若成功返回0,否则返回-1。 参数: handle:即由_findfirst函数返回回来的句柄。 fileinfo:文件信息结构体的指针。找到文件后,函数将该文件信息放入此结构体中。 int _findclose( long handle ); 返回值:成功返回0,失败返回-1。 参数: handle :_findfirst函数返回回来的句柄。 */ void showAll(char* path) { _finddata_t fileDir; long handle; char dir[MAXPATH]; strcpy(dir, path); //路径添加'/' strcat(dir, "/*.*"); //临时保存作用 char pathTemp[MAXPATH]; char nameTemp[MAXPATH]; FILE* file; char* abPath; if ((handle = _findfirst(dir, &fileDir)) != -1l) { while (_findnext(handle, &fileDir) == 0) { //是文件夹:fileDir.attrib == _A_SUBDIR if (fileDir.attrib == _A_SUBDIR && strcmp(fileDir.name, ".") != 0 && strcmp(fileDir.name, "..") != 0) { strcpy(dir, path); //strcat(dir, "/"); strcat(dir, fileDir.name); //递归文件夹 showAll(dir); } else { //是文件 if (strcmp(fileDir.name, ".") != 0 && strcmp(fileDir.name, "..") != 0) { //是c文件 if (cFile(fileDir.name) == CFILE) { strcat(path, "/"); strcpy(pathTemp, path); strcpy(nameTemp, fileDir.name); abPath = strcat(pathTemp, nameTemp); printf("\n*****************************************\n"); printf("\t%s%s\n", path, fileDir.name); file = fopen(abPath, "r"); printf("\n\t行数:%d 行\n", countLine(file)); file = fopen(abPath, "r"); printf("\n\t字符数:%d 个\n", countCharacter(file)); file = fopen(abPath, "r"); printf("\n\t词数: %d 个\n", countWord(file)); printf("\n*****************************************\n"); } } } } } _findclose(handle); }
四、测试
(1)未输入指令
(2)非C文件
(3)空文件
(4)单个字符文件
(5)单个词文件
(6)单行文件
(7)典型源文件
(8)扩展功能---递归处理目录下符合条件的文件
五、PSP表
六、项目小结
1.在编程的过程中感受到了一开始没设计好所带来的后果。在扩展递归处理功能的时候,由于做基础功能时没考虑到后续扩展的问题,因此做扩展功能时,又需要返回修改基础功能。
比如一开始将各种情况直接在各个基础功能函数里输出,但是递归处理时,又不需要对非C文件进行输出,需要将某些输出情况放到函数外面。
2.计划时间与实际消耗时间的巨大差异也反应了一开始对整个任务考虑的欠缺以及对自己能力的高估。