这个作业属于哪个课程 | 构建之法-2021秋-福州大学软件工程 |
---|---|
这个作业要求在哪里 | 2021秋软工实践第一次个人编程作业 |
这个作业的目标 | 制定个人编程规范,实现对读入的C或C++代码文件进行不同等级的关键字提取 |
学号 | 031902608 |
github个人主页:请点击这里
个人代码规范:请点击这里
PSP表格
PSP | 预估耗时(分钟) | 实践耗时(分钟) |
---|---|---|
计划 | 60 | 50 |
估计这个任务需要多少时间 | 1200 | 1120 |
开发 | - | - |
需求分析 (包括学习新技术) | 600 | 540 |
生成设计文档 | - | - |
设计复审 | - | - |
代码规范 | 80 | 80 |
具体设计 | 60 | 50 |
具体编码 | 180 | 240 |
代码复审 | 60 | - |
测试 | 60 | 30 |
报告 | 60 | 60 |
测试报告 | - | - |
计算工作量 | 20 | 30 |
总结 | 20 | 40 |
合计 | 1200 | 1120 |
解题思路
- 题目要求:
实现一个程序功能,它可以对读入的C或C++代码文件进行不同等级的关键字提取。基础要求:输出关键字统计信息
进阶要求:输出有几组switch case结构,同时输出每组对应的case个数
拔高要求:输出有几组if else结构
终极要求:输出有几组if,else if,else结构
执行时,需向程序传入两个参数,第一个参数是代码文件的路径,第二个参数是完成等级(从低到高为1,2,3,4)
-
解题思路
首先要读取txt文件,以字符串的形式读取文件,使用getline一行行读取代码,然后对每行的代码以空格为间隔分割为一个个字符串进行分析。
首先得输入文件路径,然后就是如何以字符串形式读取代码。一开始想的是整行读取,使用c++自带的find()函数进行关键词统计,但是find函数只返回这个词第一次出现的位置(没找到这个词就返回string::npos),如果一行有多个关键词就会很麻烦。于是我把一行字符串以空格为间隔进行分割,这个时候一个字符串只会出现一个关键词了。关于文件输入路径、按行读取字符串和按空格分割字符串我花了很多时间查了很多网页和资料,果然欠下的迟早要还。string file_name; cin>>file_name; ifstream myfile(file_name.c_str()); //打开文件 string temp; while(getline(myfile,temp)) //逐行读取 { istringstream is(temp); string s; while(is>>s) //去除空格,例如:if( a==b ),分为3个字符串"if(","a==b",")" { ... }
接下来就是关键词的统计部分,题目中关键词有32个,要先把关键词存起来方便之后进行关键词判断。
string key_word[32]={ "auto","break","case","char","const","continue","default","double", "do","else","enum","extern","float","for","goto","if","int","long", "register","return","short","signed","sizeof","static","struct", "switch","typedef","union","unsigned","void","volatile","while"};
然后对分割后的字符串用find()函数进行关键词判断
int Judge(string str,string str1)//在str中寻找str1 { for(a=0;a<32;a++) { int loc=str.find(str1,0); if(loc!=string::npos) { keyword_num++; //cout<<str<<endl;这行代码用来检验识别出来的关键词 return 1; } } return 0; }
但是出现了一个问题,double会被识别为do,以此类推interesting会识别出int,所以我又写了第二个函数,用来判断关键词是不是其他单词中的一部分。例如idol,识别出do的时候进行判断:do前面或者后面是否有英文字母,有的话就不算关键词do。if_else_num也会识别出关键词来,所以还要判断前后是否下划线。至于注释中有关键词那就整行跳过,字符串中有关键词的话就很麻烦了,暂时没考虑。
int Judge1(string str,string str1) //在str中识别str1 { int Judge2(char str); int loc=str.find(str1,0); int len=str1.length(); if(loc!=string::npos) { if(loc==0) { if(Judge2(str[loc+len])==0) return 1; else return 0; } else { if(Judge2(str[loc-1])==0&&Judge2(str[loc+len])==0) return 1; else return 0; } } return 0; } int Judge2(char str) { if(str>='a'&&str<='z') return 1; if(str>='A'&&str<='Z') return 1; if(str=='_') return 0; if(str>='0'&&str<='9') return 0; return 0; } void Level_1(string word1) //进行关键词统计 { for(int a=0;a<32;a++) { if(Judge1(word1,key_word[a])==1) { keyword_num++; break; } } }
然后就是switch case结构,这里是用一个flag来判断是否找到了switch,当已经找到switch的情况下,碰到case就直接switch_case[switch_num]++,如果匹配到下一个switch,就直接switch_num++,以switch来间隔case的分组。
如果switch嵌套的话?void Level_2(string word2) { if(flag) { if(Judge1(word2,"case")==1) { switch_case[switch_num]++; } } if(Judge1(word2,"switch")==1) { switch_num++; flag=1; } }
最后是if-else结构和if_elseif_else结构的统计。经观察,有if、else if、else三种结构,然后每行只会出现一种结构,所以对于这种结构的统计只需要一行行读取文档,不需要进行分割。对于if-else结构的判断类似于以前写过的括号匹配。碰到if就入栈,写入1,当碰到else的时候就出栈,然后if_else_num++。能成功编译的代码if的数量一定是>=else的数量。
对于if-elseif-else结构的话也差不多。碰到if就入栈1,碰到else if的时候要进行判断:当前栈顶是1的话就入栈2,是2的话就入栈2(if-elseif-if结构中的else if可以重复出现)。碰到else的话也要进行判断:栈顶是1的话就算if-else结构,栈顶是2的话就算if-elseif-else结构。
对于两个结构的判断条件很相似,就合在一块写了。
int ifelse_num=0; int if_elseif_else_num=0; int stack[500]={0}; int top=-1; void Level_34(string word3) { if(Judge1(word3,"else if")) { top++; stack[top]=2; } else { if(Judge1(word3,"if")) { top++; stack[top]=1; } else { if(Judge1(word3,"else")) { if(stack[top]==1) { ifelse_num++; top--; } else { if(stack[top]==2) { if_elseif_else_num++; top--; } } } } } }
性能分析
可以看出来,judge1和judge2的调用次数最多。然后函数名称为什么是乱码我也不清楚。
测试代码:老师给的样例以及我自己加以改动的样例。
源代码:https://github.com/HarmonyOfLight/-C-C-
总结
很久没写代码了,虽然思路还算比较清晰,但这次作业其实完成的很折磨,很多函数不会调用,花了大把时间去查找资料,去学习一些函数的使用。这个性能分析还是有点麻,没搞懂什么意思。收获还是不少的,学会了git的使用以及制作自己的代码规范以及PSP表,希望以后能在代码方面更进一步。