2021秋软工实践第一次个人编程作业

かくしごと 2021-09-20 00:31:03

 

2021秋软工实践第一次个人编程作业

 

这个作业属于那个课程构建之法-2021年秋-福州大学软件工程
这个作业要求在哪里2021年秋软工实践第一次个人编程作业
这个作业的目标设计程序对C语言文件的关键字进行统计、以及不同等级的分类提取、完善代码规范、GitHub使用
学号031902130

 

任务分解

  • 根据题目描述,这次任务大致可以分为四个部分:

       1.文件的读取与分析 

       2.字符分割与比对

       3.分析与计数

       4.撰写博文

 

git部分  (前置要求)

                                        我的 个人代码规范

 

psp表格

  • 制定本次任务的psp表格如下:
PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划6060
Estimate估计这个任务需要多少时间12001100
Development开发--
Analysis需求分析 (包括学习新技术)10080
Design Spec生成设计文档--
 Design Review设计复审 (审核设计文档)--
Coding Standard代码规范 (为目前的开发制定合适的规范)4050
Design具体设计200160
Coding具体编码600500
Code Review代码复审6060
Test测试(自我测试,修改代码,提交修改)6050
Reporting报告--
Test Report测试报告--
Size Measurement计算工作量5040
Postmortem & Process Improvement Plan事后总结, 并提出过程改进计划3060
 合计12001090

解题思路

1. 工作迭代过程:

2.题目分析:

①有四个等级的要求,难度不断上升,并且都包含前置等级的所有要求。(也就是说,编码也可以逐步进行,在初等级代码上不断添加新的要求)

②根据给定的关键字,统计文本中的“真关键字”(即不能包含带有关键字的变量名,函数名,注释等,如 int  int1;只有第一个int为关键字)。因此要屏蔽掉注释中的内容,可能要遇见//  或者 /*  */ 就忽略其中的内容? 以及正确提取每一个字符串,先完整提取再比对计数。

③if-else结构让我联想到了当初的堆栈,检测if - else结构之间是否存在else if 便能完成第四等级的要求(理论上说)

 

3.难点分析及解决(部分关键代码及函数思路)

①编码刚开始就遇到了第一个困难,那就是如何从文件的路径中读取内容。因为之前没有完成过类似的项目,于是我查找了各种网络上的资料。在参考 c++读取TXT文件内容 内容下,利用open函数实现了对包含空格在内的每个字符的读取,方便了之后的操作。

void readtxt(string file)
{
    ifstream infile; 
    infile.open(file.data());   //将文件流对象与文件连接起来 
    assert(infile.is_open());   //若失败,则输出错误消息,并终止程序运行 
     
	char c;
    infile >> noskipws;
    while (!infile.eof())
    {
        infile>>c;
        a[n++]=c;       //用char型数组a存储
    }
   
	 infile.close();    //关闭文件输入流 
     
    count();            //各等级计数        
}

②真正意义上的第一个难点是对文本中每一个“单词”的提取,以方便查找关键词,并排除无关项。

       首先首先我尝试利用空格或标点对每一个词进行分割,但实际操作中感觉对代码文件作为一个整体进行操作难以进行,而且很容易超时,于是我改变思路,对数组a(装有代码文件每个字符 包含空格,回车 的数组)进行逐个解析。忽略掉无用符号,在遇到第一个有效字母开始提取,直到遇到第一个不是字母或数字的内容停下里,记作一个单词。

       每个单词与关键字组进行一一对比,再进行之后的操作。

int j=0;
int getword(char *word, int lim){
    char *w = word;
    for( j;j<n;j++){
    if(isspace(a[j]))
	   continue;
	else break;//忽略空白符
    }
     
    *w++ = a[j];
    if (!isalpha(a[j])){          //当前不是字母的情况 
        *w = '\0';
        return a[j++];
    }
    j++;
    for ( ; --lim > 0; w++){
    if (!isalnum(*w = a[j++])){  //当前不是字母或数字时,该单词结束
        j--;
        break;
        }
    }
    *w = '\0';                    //字符串结束符'\0'
}

③在此之上,对if-else  if-else if-else结构的区分和统计也是一大难点,一开始我的思路是用堆栈的方法解决。但是由于我是逐个对比的方式来遍历挣个代码,因此else if的确定变得更加复杂。

我最后只能创建了一个word0数组来记录上一个单词信息,以此来区分 if、else if 和 else。并简化成特定数字入栈,遇到else出栈。但想法简单,操作起来格外复杂。由于我一开始只提取了单词,没有记录符号空格等内容,导致区分不成功,造成严重错误。在添加了wordflag标记并加入if条件里后,终于落下了帷幕。

解释1:举例来说,如果程序中出现了下方这样的代码段,我的代码会把中间的空格和大括号忽略掉,导致错误识别为else if。

else{
if(j!=0){}
}

解释2:对于if条件中的内容:第一点是当前这个单词为 “if”,第二点是(上一个单词不是else,或者和上一个单词 中间 存在括号等符号分隔开)即  if(strcmp(keytab[m].word,"if")==0&&(strcmp(word0,"else")!=0||wordflag==1))

对于switch case的统计就较为简单了,只要遇到swtich将case的计数归零并记录下来即可。swtich有几个,case就有几组。

if(strcmp(keytab[m].word,"if")==0&&(strcmp(word0,"else")!=0||wordflag==1))
	st.push(1);    //记录if为1  else if为2  else为3
if(strcmp(keytab[m].word,"if")==0&&strcmp(word0,"else")==0&&wordflag==0)
	st.push(2);  
if((strcmp(keytab[m].word,"if")!=0||wordflag==1)&&strcmp(word0,"else")==0)
	pop0();        //遇到else出栈 

每轮循环最后刷新word0以不断保存上一个单词。

strcpy(word0,keytab[m].word);

完整的计数输出函数count()。对每一个得到的word进行一一比对查找,并完成各类计数工作。以及分等级输出。(已修改结构体为数组结构)

void count(){
    int m;int wordflag=0;int sum=0;
    char word[MAX],word0[MAX];//word0记录上一个单词 
    for(int i=0;i<n;i++)
{
    getword(word, MAX);
    if (isalpha(word[0])) //word的第一个为字母
        {
		if ((m = binsearch(word, NKEYS)) >= 0)
            {sum++;//在结构体中查找成功,关键字计数加1
            
			if(strcmp(key[m],"case")==0)
			casenum[casen]++;
			if(strcmp(key[m],"switch")==0)
		    {casen++;switchnum++;
			}
			
			if(strcmp(key[m],"if")==0&&(strcmp(word0,"else")!=0||wordflag==1))
		    st.push(1);    //记录if为1  else if为2  else为3
			if(strcmp(key[m],"if")==0&&strcmp(word0,"else")==0&&wordflag==0)
		    st.push(2);  
			if((strcmp(key[m],"if")!=0||wordflag==1)&&strcmp(word0,"else")==0)
		    pop0();        //遇到else出栈 
			
			strcpy(word0,key[m]);
			}
			wordflag=0;	//还原 wordflag
		} 
	else wordflag=1;//记录上一个word为非字母 
 }
 //统计结束,打印结果

    cout<<"total num:"<<sum<<endl;
    if(level>=2){
	cout<<"switch num:"<<switchnum<<endl;
    cout<<"case num:";
    for(int k=1;k<=switchnum;k++)
    cout<<casenum[k]<<" ";
    cout<<endl;
 }
    if(level>=3){
	pop0();
 	cout<<"if-else num:"<<ifelsen<<endl;
 }
    if(level==4){
 	cout<<"if-elseif-else num:"<<ifelseifn<<endl;
 }
}

④感觉最大的难点,其实是让我一个连qq空间都发不明白的人编纂博文吧!

 

4.剩余关键代码及思路解释

①折半查找函数

    我的代码中因为对每一个单词与关键词组进行了一一对比,所以为了减少以一对比的时间,我尝试运用了折半查找函数binsearch的方法缩短运行时间(二分法)。(已修改为数组结构)

int binsearch(char *word, int n)
{
    int cond;
    int low, high, mid;
    low = 0;
    high = n - 1;
    while (low <= high)
 {
        mid = (low+high) / 2;
        if ((cond = strcmp(word, key[mid])) < 0)
            high = mid - 1;
        else if (cond > 0)
            low = mid + 1;
        else
            return mid;
    }
    return -1;
}

②出栈函数

        遇到else出栈时,对两种可能结构的计数。(函数外定义flag,ifelsen记录if-else数目,ifelseifn记录if-else-if数目

        出栈过程中,遇到else if 标记flag改为1(即if 与else中间夹杂有else if了),再遇到if是,就可以确定为if-elseif-else结构了

反之,若flag为0,即中间没有else if 那么就可以确定为if else 结构。

         找到 if 完成操作之后 退出此函数,等待下一个else的到来。

int flag=0;
int ifelsen=0,ifelseifn=0;
void pop0(){
	while(st.empty()<=0){
		int p=st.top();
		st.pop();
		if(p==2){
			flag=1;
		}
		if(p==1){
			if(flag==1){
				ifelseifn++;
				flag=0;break; 
			}		   
		    if(flag==0){
			    ifelsen++;break;
			}	    		
		}	
	}
}

③关键词组结构体(一开始为了统计方便,后来发现count用处不大,现已经修改为数组结构

struct key   //结构体数组,关键字按顺序排列
{
 const char *word;
 int count;
} keytab[] = {
"auto",0,"break",0,"case",0,"char",0,"const",0,
"continue",0,"default",0,"do",0,"double",0,
"else",0,"enum",0,"extern",0,"float",0,"for",0,
"goto",0,"if",0,"int",0,"long",0,"register",0,
"return",0,"short",0,"signed",0,"sizeof",0,
"static",0,"struct",0,"switch",0,"typedef",0,
"union",0,"unsigned",0,"void",0,"volatile",0,"while",0,
};

补:修改后的数组结构

const char * key[] ={
"auto","break","case","char","const",
"continue","default","do","double",
"else","enum","extern","float","for",
"goto","if","int","long","register",
"return","short","signed","sizeof",
"static","struct","switch","typedef",
"union","unsigned","void","volatile","while"} ;
//const char*型数组,关键字按顺序排列

 

④各种定义以及主函数

#define MAX 100000 
#define NKEYS 32  
//共32个关键字需要统计 
int n=0,level;
char a[MAX];//存储一个单词,大小在100000字符以内 
int switchnum; //记录swtich个数的变量 
int casenum[100],casen=0;//记录case个数的变量 
int ifelsen=0,ifelseifn=0;//记录if-else个数以及if-elseif-else个数的变量 
stack<int> st;//堆栈实现if else elseif的匹配问题 
int main(){
	string s;
	cin>> s;
	cin>>level;
	readtxt(s);
	return 0;
}

 

完整源代码:

 

单元测试

  •     对样例代码的测试 :

            在网上查阅了相关教程1,用VS完成了本次单元测试

                                        教程2

           点击查看样例代码

  •    对任意代码测试:

          点击查看测试代码

       可见在测试等级为4,并且样本文件足够复杂(包含所有情况时),代码覆盖率可以达到100%。

  • 性能分析

小结:  由图可知 , 占用性能最多的输入输出,其次是 readtext 函数 和  <stack> 头文件的堆栈占用了较多的性能。

 


    总结:

        这次作业任务完成花费了很多时间。撰写完成博文的这一刻距离写完代码已经很久很久了。主要是对各方面操作不太熟悉,以及长时间没写代码的生疏。git建立,博文编纂,各种专有名词学习,不知不觉的学习成长了许多。在完成整个作业的过程中,感觉到自己对程序开发有了初步了解,对问题的解决也更有信心了。但这次学习中,很多看似很简单的问题我却用了很久很久的时间,希望可以给下次的作业完成打下基础!

...全文
550 3 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
a1164520408 2021-09-29
  • 打赏
  • 举报
回复

1.总体逻辑清晰
2.分析、性能评估等内容丰富
高分

SoftwareTeacher 2021-09-22
  • 打赏
  • 举报
回复 1

你没有选择用 Python 来做题,为什么呢?

かくしごと 2021-09-22
  • 举报
回复
@SoftwareTeacher Python还不熟练,需要再学习

189

社区成员

发帖
与我相关
我的任务
社区描述
福州大学软件工程教学,推行邹欣老师“构建之法”。
软件工程 高校
社区管理员
  • Dawnfox
  • REP1USONE
  • 纪华裕
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧