C/C++:WC
GitHub项目地址:https://github.com/siuwaiyuen/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 返回更复杂的数据(代码行 / 空行 / 注释行)。
空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”。
代码行:本行包括多于一个字符的代码。
注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释:
} //注释
在这种情况下,这一行属于注释行。
二、PSP表格
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
5 |
2 |
· Estimate |
· 估计这个任务需要多少时间 |
5 |
2 |
Development |
开发 |
1200 |
1500 |
· Analysis |
· 需求分析 (包括学习新技术) |
300 |
300 |
· Design Spec |
· 生成设计文档 |
30 |
20 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
0 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
30 |
50 |
· Design |
· 具体设计 |
30 |
60 |
· Coding |
· 具体编码 |
30 |
60 |
· Code Review |
· 代码复审 |
60 |
150 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
60 |
180 |
Reporting |
报告 |
30 |
50 |
· Test Report |
· 测试报告 |
40 |
60 |
· Size Measurement |
· 计算工作量 |
10 |
10 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
10 |
10 |
合计 |
|
1840 |
2454 |
三、解题思路
由于最近在学C++,而且刚好看到文件输入/输出流的这一部分,所以就选择用C++来写。
刚开始着手写的时候其实思路并不是很明确,大部分时间都是边想边写,有时候新加入的功能会导致旧的的功能要大改,所以最后写出来的代码有点乱,而且对刚学到的知识不是很熟悉,经常性地要查阅资料,在这方面耗费了不少时间。
我的大体思路就是把这个程序分为这几个模块,主函数、文件列表获取、基础统计功能、扩展统计功能。
四、设计实现过程
主要函数
void getFiles(string path, string path2, vector<string>& files); void basicCount(string filename, int *cnt); void extendedCount(string filename, int *cnt);
主函数
主函数主要功能是对控制台中获取的用户输入的命令行参数进行分析,若参数符合条件,则执行相对应的功能,例如接收到命令-w,将bool _w的值置为true,则可以输出相应文件的词数。
文件列表获取
由于并不太熟悉这方面的内容,书上也没有关于这方面的函数,只好在网上找了一个getFile的函数来对当前目录及其子目录的所有文件名进行获取。
基础统计功能
该模块主要是对词数、字符数、行数进行统计,这里我采取的是逐字符读取的方法,当isgraph()为true时,字符数+1;而当字符从可显示字符转变为不可显示字符,词数+1;当字符为‘\n’时,行数+1;这里要注意如果文件的倒数第二个字符不是'\n'的话,文件的最终行数还要+1.这里我的主要问题是一开始不知道inFile>>ch;会自动过滤到空白字符,导致测试结果一直出问题,后来查阅资料发现inFile >> noskipws;可以完美解决这个问题。
扩展统计功能
该模块的功能是对文件的代码行,注释行和空行进行统计。在这个统计函数里我用了几个flag。
int flagSlah = 0, flagCode = 0, flagComment = 0; // /*标记 代码标记 注释标记
这里我用的是逐行读取+逐字符读取的方法。当注释标记为0且行内的非空白字符数小于等于1则改行判定为空行,空行数+1;
注释行的统计分为两种情况:
1.”//“注释:这种情况比较简单,只要遇到“//”就将注释行行数+1,改行也不必再遍历,直接break跳到下一行的读取;
2“/**/”注释:这种情况就稍微麻烦一点,这里我用到了注释标记和代码标记,只有遍历到行尾部时才将行数+1,避免了一行被重复统计多遍(具体实现看下面的代码)
五、主要函数代码
void getFiles(string path, string path2, vector<string>& files) { //句柄信息 long hFile = 0; struct _finddata_t fileinfo; //文件信息 string p, p2, suffixName; if ((hFile = _findfirst(p.assign(path).append(path2).append("*").c_str(), &fileinfo)) != -1) { do {//如果是目录,迭代之 //如果不是,加入列表 if ((fileinfo.attrib & _A_SUBDIR)) { if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0) getFiles(p.assign(path).append("\\"), p2.assign(fileinfo.name).append("\\"), files); } else { files.push_back(p.assign(path2).append(fileinfo.name)); //相对路径 } } while (_findnext(hFile, &fileinfo) == 0); _findclose(hFile); } }
void basicCount(string filename, int *cnt) { char ch; char lch = '\0'; fstream inFile; inFile.open(filename); if (!inFile.is_open()) { cerr << "Open Failed." << endl; exit(1); } bool wFlag = false; bool mark; inFile >> noskipws; //不过滤空白字符 inFile >> ch; while (!inFile.eof()) { if (inFile.good()) { mark = (ch == ' ' || ch == ';' || ch == ',' || ch == '\t' || ch == '\n' || ch == '(' || ch == ')'); if (mark && wFlag) cnt[1]++; //词数 wFlag = !mark; if (isgraph(ch)) cnt[0]++; //字符数 if (ch == '\n') cnt[2]++; //行数 lch = ch; } inFile >> ch; } inFile.close(); if (lch != '\n') cnt[2]++; return; }
void extendedCount(string filename, int *cnt) { string line; int flagSlah = 0, flagCode = 0, flagComment = 0; int i, nBoth = 0; fstream inFile; inFile.open(filename); if (!inFile.is_open()) { cerr << "Open Failed." << endl; exit(1); } while (!inFile.eof()) { getline(inFile, line); i = 0; flagCode = 0; flagComment = 0; if (flagSlah) flagComment = 1; while (line[i] == ' ' || line[i] == '\t' || line[i] == '\r' || line[i] == '\n') ++i; if (!flagSlah && (line[i] == '\0' || line[i + 1] == '\0')) { cnt[3]++; continue; } while (1) { if (!flagSlah && line[i] == '/' && line[i + 1] == '/') { if (flagCode) { cnt[4]++; //注释行 cnt[5]++; //代码行 nBoth++; //公共行 } else //纯注释行 { cnt[4]++; } break; } //多行注释开始 if (!flagSlah && line[i] == '/' && line[i + 1] == '*') { i += 2; flagSlah = 1; flagComment = 1; continue; } //多行注释 if (flagSlah) { //"多行注释结束" if (line[i] == '*' && line[i + 1] == '/') { i++; flagSlah = 0; } else if (line[i] == '\0') { if (flagCode) { cnt[4]++; //注释行 cnt[5]++; //代码行 nBoth++; //公共行 } else { ++cnt[4]; //纯注释行 } break; } ++i; continue; } if (line[i] == '\0') { if (flagComment) cnt[4]++; if (flagCode) cnt[5]++; if (flagComment && flagCode) nBoth++; break; } i++; flagCode = 1; //运行至此,说明这一定是代码行 } } inFile.close(); }
主函数
int main(int argc, char* argv[]) { bool _w = false, _c = false, _l = false, _a = false, _s = false; int i; for (i = 1; i < argc - 1; i++) { if (!strcmp(argv[i], "-s")) _s = true; if (!strcmp(argv[i], "-c")) _c = true; if (!strcmp(argv[i], "-w")) _w = true; if (!strcmp(argv[i], "-l")) _l = true; if (!strcmp(argv[i], "-a")) _a = true; } vector<string> files; char buffer[MAX_PATH]; _getcwd(buffer, MAX_PATH); int cnt[6] = { 0 }; string filePath;if (_s) { filePath.assign(buffer).append("\\"); getFiles(filePath, "", files); int size = files.size(); for (i = 0; i < size; i++) { cout << files[i].c_str() << endl; basicCount(files[i].c_str(), cnt); extendedCount(files[i].c_str(), cnt); if (_w) cout << "words:" << cnt[1] << endl; if (_c) cout << "characters:" << cnt[0] << endl; if (_l) cout << "lines:" << cnt[2] << endl; if (_a) { cout << "blank lines:" << cnt[3] << endl; cout << "comments lines:" << cnt[4] << endl; cout << "codes lines:" << cnt[5] << endl; }
cout << endl;
memset(cnt, 0, sizeof(cnt));
} } else { basicCount(argv[argc - 1], cnt); extendedCount(argv[argc - 1], cnt); if (_w) cout << "words:" << cnt[1] << endl; if (_c) cout << "characters:" << cnt[0] << endl; if (_l) cout << "lines:" << cnt[2] << endl; if (_a) { cout << "blank lines:" << cnt[3] << endl; cout << "comments lines:" << cnt[4] << endl; cout << "codes lines:" << cnt[5] << endl; } } system("pause"); return 0; system("pause"); return 0; }
注:因为编码问题,fstream读取的文件中如果含中文会弹框报错,所以GitHub中代码的注释改为英文。
六、测试运行
七、项目小结
这是第一次用GitHub来做这样一个项目,其实过程问题挺多的,最后写出来的代码有点乱而且还存在挺多bug的,有的是对主要功能影响不大不够时间改,有的是不会改或者是改起来要大幅修改代码的,所以程序还存在些问题的。做这个项因为经验不足,再加上知识储备完全不足,基本上算是边做项目边学的,虽然最后做完有点累,但能学到挺多东西又能积累到经验还是挺不错的。