个人项目作业
一、github地址
https://github.com/Chendabaiiii/wordCounter
二、PSP表格(估计)
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
30 |
|
Estimate |
估计这个任务需要多少时间 |
30 |
|
Development |
开发 |
1440 |
|
Analysis |
需求分析 (包括学习新技术) |
120 |
|
Design Spec |
生成设计文档 |
60 |
|
Design Review |
设计复审 (和同事审核设计文档) |
30 |
|
Coding Standard |
代码规范 (为目前的开发制定合适的规范) |
30 |
|
Design |
具体设计 |
60 |
|
Coding |
具体编码 |
1000 |
|
Code Review |
代码复审 |
60 |
|
Test |
测试(自我测试,修改代码,提交修改) |
30 |
|
Reporting |
报告 |
60 |
|
Test Report |
测试报告 |
40 |
|
Size Measurement |
计算工作量 |
30 |
|
Postmortem & Process Improvement Plan |
事后总结, 并提出过程改进计划 |
60 |
|
合计 |
|
3080 |
|
三、解题思路
1. 编程语言的选择
一开始注意到此软件是一个命令行程序,可能需要用到 cmd 命令行传参来运行,之前学C语言时并没有了解过此类方法。自己本身的技术栈也只是页面上的开发,并没有学过利用前端三剑客开发桌面应用的相关技术。后来了解了一项可以结合HTML、CSS、JS开发桌面应用的Electron技术后,又想起有个遍历文件/选择文件的功能,这个时候js可能没有优势,想到可能会踩很多坑,于是决定用C++/C作为技术栈边学边用开发此软件。
四、设计实现过程
1. 代码的组织
首先我把主函数文件和工具函数分开,在主函数进行工具函数的调用;
工具函数中的核心函数有:
int getWordNumInLine(char* str); // 计算单行单词个数
void trim(string& str); // 去除字符串的头尾空格
void characterCount(string fileName); // 计算字符个数
void wordCount(string fileName); // 计算单词个数
void lineCount(string fileName); // 记算行数
void complexLineCount(string fileName); // 返回更复杂的数据(代码行 / 空行 / 注释行)
bool judgeComment(string line, bool& isInComment); // 判断是不是在注释行内
五、代码说明
getWordNumInLine
// 计算文件的字符个数 void characterCount(string fileName) { int charNum = 0; // 记录字符数目 char line[256]; fstream file; // 文件流 file.open(fileName, ios::in); // 采用读取方式打开文件 while (!file.eof()) { file.getline(line, 256, '\n'); // 该行字符达到256个或遇到换行就结束 int len = strlen(line); charNum += len; for (int i = 0; i < len; i++) { // 如果是中文则要减1,因为中文算是双字节 if (line[i] < 0 && line[i + 1] < 0) { charNum--; i++; } } } file.close(); cout << "该文件共有" << charNum << "个字符" << endl; };
characterCount
// 计算单行单词个数 int getWordNumInLine(char* str) { int cnt = 0, i = 0; int len = strlen(str); for (i = 0; i < len - 1; i++) { // 字母 + 非字母 则为一个单词的结束 if (isalpha(str[i]) && (!isalpha(str[i + 1]))) cnt++; } // 以两个字母结束的情况 if (isalpha(str[i])) cnt++; return cnt; }
wordCount
// 计算文件的单词个数 void wordCount(string fileName) { int wordNum = 0; // 记录单词数目 char line[256]; fstream file; // 文件流 file.open(fileName, ios::in); // 采用读取方式打开文件 while (!file.eof()) { file.getline(line, 256, '\n'); // 该行字符达到256个或遇到换行就结束 wordNum += getWordNumInLine(line); } file.close(); cout << "该文件共有" << wordNum << "个单词" << endl; }
lineCount
// 计算文件的行数 void lineCount(string fileName) { int lineNum = 0; // 记录行数目 char line[256]; fstream file; // 文件流 file.open(fileName, ios::in); // 采用读取方式打开文件 while (!file.eof()) { file.getline(line, 256, '\n'); // 该行字符达到256个或遇到换行就结束 lineNum++; } file.close(); cout << "该文件共有" << lineNum << "行" << endl; }
judgeComment
// 判断是不是在注释内 // 返回true表示该行是注释行 bool judgeComment(string line, bool& isInComment) { regex pattern1("^[{}]?[\\s]*\\/\\/[^\\n]*"); // 为"//"、"{...//"、"}...//"的行级注释 regex pattern2("^\\*\\/[//s]*[{}]?$"); // 为"*/"、"*/...{"、"*/...}" regex pattern3("\\/\\*[^\\*^\\/]*\\*\\/"); // 匹配 /**/ regex pattern4("^[{}]?[//s]*\\/\\*[^\\n]*"); // 匹配 "/*..."、"{/*..."、"}/*..." if (regex_match(line, pattern1)) return true; // 是行级注释 if (regex_match(line, pattern2)) { isInComment = false; // 没在注释内但是该行算注释 return true; } // 如果在注释内,而且该行找不到 */ 块注释结束行 if (isInComment && line.find("*/") == string::npos) { return true; } string replaceStr = regex_replace(line, pattern3, ""); // 去除字符串中所有 /**/对 if (regex_match(replaceStr, pattern4)) { isInComment = true; return true; } return false; }
complexLineCount
// 返回更复杂的数据(代码行 / 空行 / 注释行) void complexLineCount(string fileName) { int codeLine = 0, // 代码行 emptyLine = 0, // 空行 commentLine = 0; // 注释行 bool isInComment = false; // 是否在注释块里 bool isEmptyLine = false; // 是否是空行的标志 regex isEmpty("^[{}]?$"); // 是否是空行的正则 char line[256]; fstream file; // 文件流 file.open(fileName, ios::in); // 采用读取方式打开文件 while (!file.eof()) { isEmptyLine = false; file.getline(line, 256, '\n'); // 该行字符达到256个或遇到换行就结束 int len = strlen(line); string strLine(line); // 将char数组转为string trim(strLine); // 注释块里面的空行不包括 if (!isInComment && regex_match(line, isEmpty)) { emptyLine++; // 不在注释块内且长度小于1则为空行 isEmptyLine = true; } // 如果是注释行 if (judgeComment(strLine, isInComment)) { commentLine++; } else if(!isEmptyLine){ // 如果不是注释行而且不是空行,那就是代码行 codeLine++; } } file.close(); cout << "该文件共有" << endl << codeLine << "代码行" << endl << emptyLine << "空行" << endl << commentLine << "注释行" << endl; }
main方法
// 主函数 // argc 是命令参数数目 // argv 接收命令参数字符串, argv[0] 默认为exe文件路径 int main(int argc, char** argv) { if (argc < 3) { cout << "参数有误,请输入正确的参数" << endl; return 0; } // 需要外传入两个参数,一个是模式,一个是文件名 string mode = argv[1]; string fileName = argv[2]; setlocale(LC_ALL, "chs"); switch (mode[1]) { // 字符计数 case 'c': { characterCount(fileName); break; } // 单词计数 case 'w': { wordCount(fileName); break; } // 行数 case 'l': { lineCount(fileName); break; } // 返回更复杂的数据(代码行 / 空行 / 注释行) case 'a': { complexLineCount(fileName); break; } default: { break; } } return 0; }
六、测试运行
功能实现:本程序只实现了 -c、-w、-l、-a的功能指令
测试使用样例
int a;//what /**/int b; // 6 void test() { for (;;) { } } // aaaaa /* */
测试结果
-c(计算字符数)
-w(计算单词数)
-l(计算行数)
-a(复杂行数计算)
七、PSP表格(实际)
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
30 |
20 |
Estimate |
估计这个任务需要多少时间 |
30 |
20 |
Development |
开发 |
1440 |
780 |
Analysis |
需求分析 (包括学习新技术) |
120 |
200 |
Design Spec |
生成设计文档 |
60 |
0 |
Design Review |
设计复审 (和同事审核设计文档) |
30 |
20 |
Coding Standard |
代码规范 (为目前的开发制定合适的规范) |
30 |
20 |
Design |
具体设计 |
60 |
10 |
Coding |
具体编码 |
1000 |
780 |
Code Review |
代码复审 |
60 |
60 |
Test |
测试(自我测试,修改代码,提交修改) |
30 |
60 |
Reporting |
报告 |
60 |
60 |
Test Report |
测试报告 |
40 |
20 |
Size Measurement |
计算工作量 |
30 |
30 |
Postmortem & Process Improvement Plan |
事后总结, 并提出过程改进计划 |
60 |
20 |
合计 |
|
3080 |
2100
|
八、项目小结
第一次使用c语言开发一个小小小程序,有很多做的不好的地方。code review之后觉得自己的代码封装性不强,代码覆盖率不高,唯有仅仅将功能函数与主函数进行分离。
由于以前很少接触书本以外的API,要实现一些功能需要查阅好些资料。此次遇到比较大的困难是注释行的计算,由于思考过后发现将会有很多的情况,于是后来考虑使用正则表达式来匹配,但由于正则接触的不多,平时也比较少使用,基本上就忘了,经过这个功能的实现,也相当于重温了一下正则表达式。也经过了这次项目的开发,发现自己技术上还有太多太多的缺陷。由于技术的限制、其他不可抗力因素以及自己的拖延症,而导致最终只能实现这几个小小的功能。
也让我清楚的知道了自己的编程之路还很长,于是我将反思后在下个项目付出更多的努力!