单词计数——软件工程结对项目实记

单词计数——软件工程结对项目实记

1. 项目描述

Github地址:WordCount - Github

和队友分析了各个题目以后,结合需求与能力,最终选择了较为简单的第一道题目。
通过一条条的需求分析,本次软工项目需要实现以下功能。

  1. 统计单一文件的词数、字符数、行数,还需能够显示行的具体信息,如代码行、空行、注释行的数目
  2. 递归处理目录下所有合法文件的基础统计结果
  3. 在图形界面显示文件,通过点击文件显示文件具体统计结果,并返回当前目录及所有子目录中代码文件的具体行信息。

本次的代码在实现过程中,通过对困难的攻关,以及前辈的指导下,很多功能的实现,都借助C++自带的函数,通过对文件流和数据流的操作来完成。再次也感谢前辈一直的帮助。

2. PSP 2.1 表格

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划2030
· Estimate· 估计这个任务需要多少时间2030
Development开发14703470
· Analysis· 需求分析(包括学习新技术)200300
· Design Spec· 生成设计文档6060
· Design Review· 设计复审(和同事审核设计文档)2020
· Coding Standard· 代码规范(为目前的开发制定合适的规范)3030
· Design· 具体设计6060
· Coding· 具体编码600900
· Code Review· 代码复审300400
· Test· 测试(自我测试,修改代码,修改提交)200300
Reproting报告170250
·Test Report· 测试报告120200
·Size Measurement· 计算工作量3030
·Postmortem & Process Improvement Plan· 事后总结,并提出改进计划2030
合计16606110

3. 确定解题思路

统计功能实现:

关于代码的词数,字符数,以及行数,都可以通过判断读入的字符来加以辨别及计数。
字符数是最简单的,读入不为空即加一。
词数这里有点歧义,因为不确定这里要求的单词是不同的单词,还是只要出现过就需要计数的单词。为了设计简单,采用后种理解。将特殊字符转为空格后,开始读入,当读到字母时,确认为单词,再读入过空格判断单词何时停止并将单词数加一。
涉及到行数就更简单了,只需要通过读入换行符的个数就可以判断行数。
在复杂统计中,需要判断此行为空行还是注释行还是代码行并对应加一,这是只需要判断每行非空字符的前几位就可加以判断。

递归处理功能实现:

设计递归处理模块,每次打开所在文件夹的一个代码文件,进行信息统计并返回统计结果。

图形界面处理实现:

这里在经过查资料学习后,发现实现这一功能需要首先获取Windows系统路径,再通过对选取的文件进行统计。
因此在代码头文件库中加入包含获取系统路径函数的头文件,#include <locale.h>和 #include <ShlObj.h>。

4. 实验具体设计

我们经过分析,根据我们需要的功能,明确简洁的设计出了七个函数,不同的函数对应各自的功能,如下图所示,具体设计将在代码说明中详细介绍。
在这里插入图片描述

5.性能分析及优化

  • 优化这里需要重点讲以下,按照原来的设想,挨个字符读入,代码可能会有几百行,在后来探索文件处理方法时,偶然发现了一种又快又好的文件流操作方式,对输入流操作:seekg()与tellg(),对输出流操作:seekp()与tellp()。只要弄懂这种操作方式,就可以省去很多行代码,并且通过内置的提前编译过的函数,提升系统性能。
  • 此外,cin 搭配上>>流操作符可以从文件缓冲区流里提取数据,由于对>>进行了重载,所以可以提取不同形式的数据。

性能分析工具为Visual Stdio 自带分析工具,以下是各项功能的运行时间及函数使用情况。
可以看出,当调用程序时,首先通过main函数调用具体功能实现函数,不同的功能调用情况下,除main函数外,各函数使用情况相差较大,具体可见以下各图。

① -a
在这里插入图片描述
② -c-c
③ -l在这里插入图片描述
④ -s在这里插入图片描述⑤ -w-w⑥ -x在这里插入图片描述

6.代码说明

  1. 首先确定程序整体框架,定义所需要使用的各个函数,以及重要变量,具体的函数和变量功能写在了注释中,可以清楚明了的看到它们的作用。

各函数主要功能
设置全局变量并初始化
3. 具体功能函数的解释:七个函数的简单说明
①函数 void open_file(char *filename):这个函数比较简单,基本操作打开文件。
②函数int count_of_character(char *filename):这个函数的作用是统计字符个数,通过调用C++自带的tellg()函数,直接获得文件的字符元数。
在这里插入图片描述
③函数int count_of_character(char *filename):这个函数的作用是统计文件中的单词数,cin 搭配上>>流操作符 就是从缓冲区流里提取数据,由于对>>进行了重载,所以可以提取不同形式的数据。本函数中通过对字符串的读入来判断单词数量。

在这里插入图片描述
④函数int count_of_line(char *filename,bool print):这个函数的作用是统计文件中的行数,通过逐行读取实现行数增加。
在这里插入图片描述
⑤函数void count_of_more(char *filename,int *c,int *n,int *e):这个函数的作用是分别统计空白行,代码行,注释行。通过对读入的判断,不同情况下的行分到不同的类目中去。

void	count_of_more(char *filename,int *c,int *n,int *e){
	int empty_line = 0;					//	空白行
	int code_line = 0;					//	代码行
	int note_line = 0;					//	注释行
	char buffer[1024];
	ifstream file(filename);
	while (!file.eof()){
		memset(buffer, 0, sizeof(buffer));			//	清零缓冲区
		file.getline(buffer, 1024);					//	读取一行数据

		if (buffer[0] == '\0')								//	读取的首字符 为 空
			empty_line++;									
		else{												//	读取的首字符 非 空
			int empty = 0;							//	记录 空格 或 水平制表符
			int note = 0;							//	记录 注释行
			int code = 0;							//	记录 代码行
			int i = 0;
			for (; buffer[i] != '\0'; i++){
				if (buffer[i] == ' ' || buffer[i] == '\t')	//	出现 空格 水平制表符 记录
					empty++;
				else if (buffer[i] == '/')					//	先出现 //
					note++;
				else if (buffer[i] != '/' && buffer[i] != '{' && buffer[i] != '}')		//	先出现 其他字符
					code++;
				if (note > code){
					note_line++;
					i = -1;
					break;
				}
				if (code > note){
					code_line++;
					i = -1;
					break;
				}
			}
			if (empty == i || empty == i - 1)
				empty_line++;
		}
			}
	cout << filename << " 文件的 代码行为 " << code_line << endl;
	cout << filename << " 文件的 注释行为 " << note_line << endl;
	cout << filename << " 文件的 空行为 " << empty_line << endl;
	*c = code_line;
	*n = note_line;
	*e = empty_line;
}

⑥函数void recursion_function(char *parameter, char *filename):这个函数用来实现递归操作,最开始对递归操作的思考是件很让人头痛的事,再不同的学习新知识中,终于慢慢把这个问题解决掉了。由于加入了错误文件名判断,因此报错能力增强。

void	recursion_function(char *parameter, char *filename){							
	intptr_t	file;
	_finddata_t fileData;

	file = _findfirst(filename, &fileData);			// 查找与输入匹配的第一个文件
	if (file == -1)
	{
		cout << "当前目录下无与" << filename << "匹配的文件" << endl;
		return;
	}

	do
	{
		char *_filename = fileData.name;	//	获取文件名称
		int count_char = 0;				//	字符数统计
		int count_line = 0;				//	行数统计
		int count_word = 0;				//	单词数统计
		int ount_code_line = 0;
		int count_note_line = 0;
		int count_empty_line = 0;

		if (!strcmp(parameter, "-c")){
			count_char = count_of_character(_filename);				//	执行 字符统计操作
			cout << _filename << " 文件的 字符数为 " << count_char << endl;
		}
		else if (!strcmp(parameter, "-l")){
			count_line = count_of_line(_filename);					//	执行 行数统计操作
			cout << _filename << " 文件的 行数为 " << count_line << endl;
		}
		else if (!strcmp(parameter, "-w")){
			count_word = count_of_word(_filename);					//	执行 单词统计操作
			cout << _filename << " 文件的 单词数为 " << count_word << endl;
		}
		else if (!strcmp(parameter, "-a")){
			count_of_more(_filename, &count_code_line, &count_note_line, &count_empty_line);									//	执行 更多复杂操作
		}
		
	} while (!_findnext(file, &fileData));		// 查找目录中的下一个文件
}

⑦函数void file_dialog():这个函数用来实现界面操作。因为获取系统文件目录并在界面显示,然后进行操作这样的功能是第一次接触,在前辈的指导下慢慢把这个东西做了出来,具体的语句功能都及时写在了注释里,避免遗忘。

void	file_dialog(){

	int count_char = 0;				//	字符数统计
	int count_line = 0;				//	行数统计
	int count_word = 0;				//	单词数统计
	int count_code_line = 0;
	int count_note_line = 0;
	int count_empty_line = 0;
	wchar_t buffer[1024];

	_tsetlocale(LC_CTYPE, TEXT(""));					//让wprintf 支持中文
	TCHAR szPathName[MAX_PATH*MAX_FILE_FOR_SEL];
	OPENFILENAME ofn = { OPENFILENAME_SIZE_VERSION_400 };

	ofn.hwndOwner = GetForegroundWindow();					// 打开OR保存文件对话框的父窗口
	ofn.lpstrFilter = TEXT("文本文件(*.txt)/0*.txt/0C/C++源文件(*.cpp;*.c;*.h)/0*.cpp;*.c;*.h/0All Files(*.*)/0*.*/0/0");

	lstrcpy(szPathName, TEXT(""));
	ofn.lpstrFile = szPathName;
	ofn.nMaxFile = sizeof(szPathName);					//存放用户选择文件的 路径及文件名 缓冲区
	ofn.lpstrTitle = TEXT("选择文件");					//选择文件对话框标题
	TCHAR szCurDir[MAX_PATH];
	GetCurrentDirectory(sizeof(szCurDir), szCurDir);
	ofn.lpstrInitialDir = szCurDir;						//设置对话框显示的初始目录
	ofn.Flags = OFN_EXPLORER | OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST;	//如果需要选择多个文件 则必须带有  OFN_ALLOWMULTISELECT标志
	_tprintf(TEXT("select file/n"));

	there:
	BOOL bOk = GetOpenFileName(&ofn);					//调用对话框打开文件
	if (bOk)
	{
		count_char = count_of_character(wchar2char(szPathName));				//	统计字符个数
		count_line = count_of_line(wchar2char(szPathName));					//	统计行数
		count_word = count_of_word(wchar2char(szPathName));					//	统计单词个数
		count_of_more(wchar2char(szPathName), &count_code_line, &count_note_line, &count_empty_line);		//	统计更多数据
		swprintf_s(buffer, 1024, L"文件的 字符数 为 %d\n文件的 行数 为 %d\n文件的 单词数 为 %d\n文件的 代码行 为 %d\n文件的 注释行 为 %d\n文件的 空白行 为 %d"
			, count_char, count_line, count_word, count_code_line, count_note_line, count_empty_line);
		MessageBox(NULL, buffer, L"统计", MB_OK);

		if (IDYES == MessageBox(NULL, L"是否继续查看其他文件信息?", L"统计", MB_YESNO)){
			goto there;
		}
	}
}

7.实现结果及示例

①:基础功能
在这里插入图片描述②:扩展功能1——行数详细信息
在这里插入图片描述③:扩展功能2——递归统计
在这里插入图片描述④:高级功能——图形界面
在这里插入图片描述⑤:高级功能——文件统计
在这里插入图片描述⑥:高级功能——文件统计结果
在这里插入图片描述
⑦:高级功能——界面递归确认
在这里插入图片描述⑧:高级功能——递归显示文件信息
在这里插入图片描述

8.收获感悟

这次的项目很奇特,在最开始的时候,观察项目难度好像不大,结果越做越难,最终拖到压线惊险完成,这里很感谢队友SEM同学,一直耐心整理相关信息,不断及时提醒我各种节点的ddl,我们才能最终完成任务,也感谢前辈!!前辈的很多指点,让我少走了很多弯路。
经过了这次此项目,我有了以下这样的收获:

  1. 需求分析一定要从最开始就做好,匹配自己的能力及时获取新知识,才能跟上自己的时间安排,不至于手忙脚乱。
  2. 磨刀不误砍柴工!!对一种语言的探索是永无止境的,一个项目是一次挑战,这次挑战让我对C++有了更深的理解,包括学到了文件流数据流操作,系统路径获取等等,这对我以后的编程一定会大有帮助。
  3. 在新知识的学习方面希望自己可以更深入,这次项目因为时间问题,只是浅显的知道了怎么用,会有什么效果,却不知其所以然,因此不好保证在不看资料的情况下,还能完成的这么顺利。
  4. 结对项目和个人项目真的很不一样,沟通交流很重要,后期因为自己的时间很紧,队友也是一直监督着让我不能拉下工作,我觉得我们能完成这次项目,真的很棒。

以上就是我的收获和感想啦,居然有点开始喜欢上做软工项目了,真奇怪。
那就先给大家拜个早年吧,祝大家新春愉快,软工全过!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值