个人项目作业
1.Github地址
https://github.com/CLSgGhost/SE_work
2.项目相关需求
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 文件的代码行数、空行数、注释行数。
3.PSP估计开发时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
---|---|---|
Planning | 计划 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 20 |
Development | 开发 | 635 |
· Analysis | · 需求分析 (包括学习新技术) | 240 |
· Design Spec | · 生成设计文档 | 20 |
· Design Review | · 设计复审 (和同事审核设计文档) | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 5 |
· Design | · 具体设计 | 60 |
· Coding | · 具体编码 | 120 |
· Code Review | · 代码复审 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 |
Reporting | 报告 | 60 |
· Test Report | · 测试报告 | 20 |
· Size Measurement | · 计算工作量 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 |
合计 | 715 |
4.解题思路
- 使用的编程语言:以往都是用C写代码,这次项目尝试用C++写一下。
- 需要学习到的知识:string流的使用、正则表达式等。
- 寻找资料的方式:通过博客学习,实在难以理解时询问有经验的朋友。
- 思考细节:因为要做的是一个word count程序,而word count是存在的一个工具,可以通过了解word count的详细功能来进一步理解需求,并模 仿word count完成自己的程序。
- 遇到的困难:以前没有接触到处理文件的程序写法,各种处理文件的函数处理方式十分陌生,容易遇到难以发现的bug。
5.设计实现过程
- 输入输出:按照需求上的输入方式制定用户输入格式,给出指令菜单,然后按指令给出相关输出。
- 功能实现方式:需要实现的功能很明确,代码量应该不会很大,采用一个功能一个函数的方式实现,通过主函数调用各个功能的函数。 将所有 函数定义在一个类里面,主函数直接调用。
- 功能函数定义:
int Char_count(string File_name); //统计字符数
int Word_count(string File_name); //统计单词数
int Line_count(string File_name); //统计行数
int NullLine_count(string File_name); //统计空行数
int ComLine_count(string File_name); //统计注释行数
int CodeLine_count(string File_name, int L1, int L2); //统计代码行数 - 实现细节: 注意到 行数 = 空行数 + 注释行数 + 代码行数,干脆只实现行数、空函数和注释行数的统计函数,剩下代码行数的统计可以直接用 行数 - 空行数 - 注释行数 得到,减少开发时间。
6.代码说明
//主函数
1 int main() 2 { 3 char c; 4 Word_Count wc; 5 ifstream file; 6 string Order, File_name; 7 cout << "输入格式:wc.exe(空格)[parameter](空格)[file_name]" << endl << endl; 8 cout << "功能列表:-c 统计文件字符数" << endl; 9 cout << " -w 统计文件单词数" << endl; 10 cout << " -l 统计文行数数" << endl; 11 cout << " -a 统计文件空行数、注释行数、代码行数" << endl << endl; 12 while (1) 13 { 14 cout << "wc.exe" << " "; 15 cin >> Order >> File_name; 16 file.open(File_name, ios::in); 17 if (!file.is_open()) //查找文件失败 18 { 19 cout << "文件不存在,无法打开" << endl << endl; 20 continue; 21 } 22 c = file.get(); 23 if (c == EOF) //文件为空 24 { 25 cout << "文件为空" << endl; 26 } 27 if (Order[0] == '-') 28 { 29 switch (Order[1]) 30 { 31 case 'c':wc.Ch_count = wc.Char_count(File_name); //统计字符数 32 cout << "文件字符数为:" << wc.Ch_count << endl << endl; 33 break; 34 case 'w':wc.Wo_count = wc.Word_count(File_name); //统计单词数 35 cout << "文件单词数为:" << wc.Wo_count << endl << endl; 36 break; 37 case 'l':wc.Li_count = wc.Line_count(File_name); //统计行数 38 cout << "文件总行数为:" << wc.Li_count << endl << endl; 39 break; 40 case 'a': 41 wc.NuLi_count = wc.NullLine_count(File_name); //统计空行数 42 wc.CmLi_count = wc.ComLine_count(File_name); //统计空行数 43 wc.CdLi_count = wc.CodeLine_count(File_name, wc.NuLi_count, wc.CmLi_count);//统计代码行数 44 cout << "文件空行数为:" << wc.NuLi_count << endl; 45 cout << "文件注释行数为:" << wc.CmLi_count << endl; 46 cout << "文件代码行数为:" << wc.CdLi_count << endl << endl; 47 break; 48 default: 49 cout << "无效指令" << endl << endl; 50 break; 51 } 52 } 53 file.close(); 54 } 55 system("pause"); 56 return 0; 57 }
//统计字符数
1 int Word_Count::Char_count(string File_name) //统计字符数 2 { 3 int n1 = 0; 4 char ch; 5 ifstream f; 6 f.open(File_name, ios::in); 7 f >> ch; 8 while (!f.eof()) 9 { 10 f >> ch; //逐个读入字符并统计数量 11 n1++; 12 } 13 f.close(); 14 return n1; 15 }
//统计单词数
1 int Word_Count::Word_count(string File_name) //统计单词数 2 { 3 int n2 = 0; 4 char ch; 5 ifstream f; 6 f.open(File_name, ios::in); 7 while (!f.eof()) 8 { 9 ch = f.get(); 10 if ((ch >= 'A'&& ch <= 'Z') || (ch >= 'a'&& ch <= 'z')) //发现单词 11 { 12 while ((ch >= 'A'&& ch <= 'Z') || (ch >= 'a'&& ch <= 'z')) 13 { 14 ch = f.get(); //读取字符直到退出单词 15 } 16 n2++; //单词计数器+1 17 } 18 } 19 f.close(); 20 return n2; 21 }
//统计行数
1 int Word_Count::Line_count(string File_name) //统计行数 2 { 3 int n3 = 0; 4 char ch; 5 ifstream f; 6 f.open(File_name, ios::in); 7 ch = f.get(); 8 if (ch == EOF) //文件为空则返回0 9 { 10 return 0; 11 } 12 while (!f.eof()) 13 { 14 if (ch == '\n') //读取到换行符时行数+1 15 { 16 n3++; 17 } 18 ch = f.get(); 19 } 20 f.close(); 21 return n3 + 1; 22 }
//统计空行数
1 int Word_Count::NullLine_count(string File_name) //统计空行数 2 { 3 int n4 = 0, i = 0; 4 char ch; 5 char str[2000]; 6 bool is_comline = 0; //判断当前行是否在注释行"/*...*/"内 7 string s; 8 string pattern = "^[\\s]{1,}[\\S]?"; //正则表达式,匹配任何空字符一个以上和非空字符0或1个 9 regex exception(pattern); 10 fstream f; 11 f.open(File_name, ios::in); //打开文件 12 ch = f.get(); 13 if (ch == EOF) //文件为空则返回0 14 { 15 return 0; 16 } 17 while (!f.eof()) //判断文件是否读取完毕 18 { 19 getline(f, s); //逐行读取文件 20 i = 0; //i初始化 21 if (!s.empty()) 22 { 23 strcpy_s(str, s.c_str()); //string非空就则转换位字符数组 24 } 25 while (str[i] == ' ' || str[i] == '\t') //过滤空格和'\t' 26 { 27 i++; 28 } 29 if (str[i] == '/' && str[i + 1] == '*') //读取到'/*',开始注释行识别 30 { 31 is_comline = 1; 32 i += 2; 33 } 34 while (is_comline == 1 && str[i] != '\0') 35 { 36 if (str[i] == '*' && str[i + 1] == '/') //读取到'*/',结束注释行识别 37 { 38 is_comline = 0; 39 } 40 i++; 41 } 42 s.erase(0, 1); 43 if (s.empty() && is_comline == 0) //判断读取的行是否只有回车换行符 44 { 45 n4++; 46 continue; 47 } 48 if (regex_match(s, exception) && is_comline == 0) 49 { 50 n4++; //匹配任何空字1个以上,非空字符只有0或1个的空行 ,且不在注释行内则空行数增加 51 } 52 } 53 f.close(); 54 return n4; 55 }
//统计注释行数
1 int Word_Count::ComLine_count(string File_name) //统计注释行数 2 { 3 int n5 = 0, i = 0; 4 char str[2000]; 5 bool is_comline = 0; 6 ifstream f; 7 f.open(File_name, ios::in); //打开文件 8 while (!f.eof()) //判断文件是否读取完毕 9 { 10 f.getline(str, 2000); //逐行读取文件 11 i = 0; //i初始化 12 if (str[0] == '}') 13 { 14 i++; 15 } 16 while (str[i] == ' ' || str[i] == '\t') //过滤空格和'\t' 17 { 18 i++; 19 } 20 if (str[i] == '/' && str[i + 1] == '/') //读取'//' 21 { 22 if (is_comline == 0) //当前行不在注释'/*...*/'内,注释行加一 23 { 24 n5++; 25 continue; 26 } 27 } 28 if (str[i] == '/' && str[i + 1] == '*') //读取到'/*',开始注释行计算 29 { 30 is_comline = 1; 31 i += 2; 32 } 33 if (is_comline == 1) 34 { 35 n5++; //当前行在注释'/*...*/'内,注释行加一 36 } 37 while (is_comline == 1 && str[i] != '\0') 38 { 39 if (str[i] == '*' && str[i + 1] == '/') //读取到'*/',结束注释行计算 40 { 41 is_comline = 0; 42 } 43 i++; 44 } 45 } 46 f.close(); 47 return n5; 48 } //注:不考虑注释符'/*...*/'不闭合的情况。若出现'/*'而没有'*/',默认文本本身出错且'/*'以下全为注释行
//统计代码行数
1 int Word_Count::CodeLine_count(string File_name, int L1,int L2) //统计代码行数 2 { 3 int n6 = 0; 4 int L = 0; //定义总行数 5 Word_Count w; 6 L = w.Line_count(File_name); 7 n6 = L - L1 - L2; //注释行数 = 总行数 - 空行数 - 注释行数 8 return n6; 9 }
7.测试运行
- 已实现功能:
-c -w -l -a
- 具体测试截图:
//空文件测试
//单字符文件测试
//单个词文件测试
//单行文件测试
//典型文件测试
//错误输入提示
8.完整PSP
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
20 |
20 |
· Estimate |
· 估计这个任务需要多少时间 |
20 |
20 |
Development |
开发 |
635 |
725 |
· Analysis |
· 需求分析 (包括学习新技术) |
240 |
240 |
· Design Spec |
· 生成设计文档 |
20 |
20 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
10 |
10 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
5 |
5 |
· Design |
· 具体设计 |
60 |
60 |
· Coding |
· 具体编码 |
120 |
150 |
· Code Review |
· 代码复审 |
60 |
90 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
120 |
150 |
Reporting |
报告 |
60 |
90 |
· Test Report |
· 测试报告 |
20 |
20 |
· Size Measurement |
· 计算工作量 |
20 |
20 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
20 |
50 |
合计 |
715 |
835 |
9.项目小结
看到要做个人项目作业的时候其实我还是比较懵逼的,感觉还什么都不会,然后就要来写个人项目,不过还是一点点看博客,一点点学,终于算是做了出来,由于个人能力问题与时间问题,很多功能都还没实现,希望在以后的学习中能学习到其他功能的实现方法。
从PSP来看,实际开发时间远超预估,即使是预估已有所保留空余时间的情况下。从而看出两个问题:1.对自己编程水平还不够了解。2.对我个人来说项目难度超出预期。而且就开发过程来看,程序中许多写法还有很大的优化空间。希望在以后的学习里能不断磨练自己,让自己成长为一个有能力的软件开发者。