4题 程序分析
班级:------------- 姓名:------ 学号:------------------------------------- 完成日期:----------
【问题描述】
读入一个C程序,统计程序中的代码、注释、和空行的行数以及函数的个数和平均行数,并利用统计信息分析评价该程序的风格。
【基本要求】
- 把C程序文件按字符顺序读入源程序;
- 边读入程序、边识别统计代码行、注释行、空行,同时还要识别函数的开始和结束,以便统计其个数和平均行数;
- 程序的风格评分分为代码、注释和空行三个方面、每个方面分为A、B、C和D四个等级,划分标准为:
|
A级 |
B级 |
C级 |
D级 |
代码(函数平均长度) |
10~15行 |
8~9或16~20行 |
8~9或16~20行 |
8~9或16~20行 |
注释(占总行比率) |
15~25% |
10~14或26~30% |
5~9或31~35% |
<5%或>35% |
空行(占总行比率) |
15~25% |
10~14或26~30% |
5~9或31~35% |
<5%或>35% |
【测试数据】
先对较小的程序进行分析。当年的程序能正确运行时,对你的程序本身进行分析。
【实现提示】
为了实现的方便,可做以下的约定:
- 头两个字符是“//”的行称为注释行(该行不含语句)。除了空行和注释行外,其余均为代码行(包括类型定义、变量定义和函数头)。
- 每个函数代码的行数(除去空格和注释行)称为该函数的长度。
- 每行最多只有一个“{”、“}”、“switch”和“struct”(便于识别函数的结束行)。
【选作内容】
- 报告函数的平均长度。
- 找出最长函数及其在程序中的位置。
- 运行函数的嵌套定义,报告最大的函数嵌套深度。
代码:
#include<iostream>
#include<string>
#include<fstream>
#include<regex>
#include<iomanip>
#include<queue>
#include<Windows.h>
#define max 10000
//正则表达式头文件
using namespace std;
typedef struct node;
typedef node *tree;
int graph[max][max]; //函数嵌套定义邻接矩阵
int num[max];
int visited[max];
//打开文件名
string name ("test.cpp");
//括号匹配
int kuohao = 0;
//函数长度计数开关
int flag = 0;
//函数行数
int fun = 0;
//代码总行数
int line = 0;
struct result
{ //代码行
int Code = 0;
//注释行
int Comments = 0;
//空行
int Blanklines = 0;
int scode = 0;
int scomment = 0;
int sspace = 0;
//函数个数
int countfun = 0;
//函数总长度
int funlen = 0;
//最长函数
int maxlen = 0;
//函数平均长度
double avelen = 0;
//最长函数所在行数
int maxline = 0;
//记录函数名
int maxh = 0;
string funname[max];
//最长函数名
string maxfun;
}list;
/*根据获取到的数据,对程序进行评分*/
string evaluate(result l)
{
string value;
if (l.avelen >= 10 && l.avelen <= 15)
value += 'A';
else if ((l.avelen >= 8 && l.avelen < 10) || (l.avelen > 15 && l.avelen <= 20))
value += 'B';
else if ((l.avelen >= 5 && l.avelen < 8) || (l.avelen > 20 && l.avelen <= 24))
value += 'C';
else
value += 'D';
if (list.scomment >= 15 && list.scomment <= 25)
value += 'A';
else if ((list.scomment >= 10 && list.scomment < 15) || (list.scomment > 25 && list.scomment <= 30))
value += 'B';
else if ((list.scomment >= 5 && list.scomment < 10) || (list.scomment > 30 && list.scomment <= 35))
value += 'C';
else
value += 'D';
if (list.sspace >= 15 && list.sspace <= 25)
value += 'A';
else if ((list.sspace >= 10 && list.sspace < 15) || (list.sspace > 25 && list.sspace <= 30))
value += 'B';
else if ((list.sspace >= 5 && list.sspace < 10) || (list.sspace > 30 && list.sspace <= 35))
value += 'C';
else
value += 'D';
return value;
}
/*根据成绩给出相印的评语*/
string remark(char s)
{
if (s=='A')
return "Excellent";
else if (s == 'B')
return "good";
else if (s == 'C')
return "common";
else
return "bad";
}
string off(string s)
{
string cs;
int open = 0;
for (int i = 0;; i++) {
if (s[i] == '('&&open == 1) {
return cs;
}
if (open)
cs += s[i];
if (s[i] == ' '&&open==0) {
open = 1;
}
}
}
void analyze(string s)
{
//匹配到函数定义
if (regex_match(s, regex("^(\w{1,10}) ([qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_]{1,100})([qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_0123456789]{0,100})\((.{0,100})\).{0,2}"))) {
//函数个数+1
list.countfun++;
list.funname[list.countfun] = s;
//开启计数器
flag = 1;
}
if (regex_search(s, regex("\{")))
{
kuohao++;
}
if (regex_search(s, regex("\}")))
{
if (kuohao)
kuohao--;
if(kuohao==0&&flag)
{
fun++;
//加上尾花括号
list.funlen++;
if (fun > list.maxlen)
{
list.maxlen = fun;
list.maxfun = list.funname[list.countfun];
list.maxline = line-fun+1;
}
fun = 0;
flag = 0;
}
}
if (regex_search(s, regex("//")))
//注释行搜索;
list.Comments++;
else if (s == "")
//空行搜索
list.Blanklines++;
else {
if (flag)
{
for (int i = 1; i < list.countfun; i++) {
if (regex_search(s,regex(off(list.funname[i]))) != NULL) {
graph[list.countfun][++num[list.countfun]] = i;
}
}
list.funlen++;
}
list.Code++;
}
if (flag) {
fun++;
}
}
/*通过深度优先遍历寻找函数的最大深度*/
void dfs(int i) {
static int count;
visited[i] = 1;
for (int j = 1; j <= num[i]; j++) {
if (!visited[graph[i][j]]) {
count++;
dfs(graph[i][j]);
}
}
if (!num[i]) {
if (count+1>list.maxh) {
list.maxh = count + 1;
}
count = 0;
}
}
/*展示面板*/
void show()
{
//计算每个函数的最大镶嵌深度
for (int i = 1; i <= list.countfun; i++) {
dfs(i);
memset(visited, 0, sizeof(visited));
}
list.scode = (int)((double)list.Code / (list.Code + list.Comments + list.Blanklines)*100+0.5);
list.scomment = (int)((double)list.Comments / (list.Code + list.Comments + list.Blanklines) * 100 + 0.5);
list.sspace = 100 - list.scode - list.scomment;
list.avelen = (double)list.funlen / list.countfun;
string res = evaluate(list);
cout << "The result of analysing program file "" + name + "":" << endl;
cout << " Lines of code : " << list.Code << endl;
cout << " Lines of comments : " << list.Comments << endl;
cout << " Blank lines : " << list.Blanklines << endl;
cout << " Code Comments Space" << endl;
cout << " ==== ======== =====" << endl;
cout << " " <<list.scode<< "% "<<setw(6)<<list.scomment << "% " << list.sspace<<"%"<< endl;
cout << " The program include " << list.countfun << " functions." << endl;
cout << " The average length of a section of code is " <<list.avelen<< " lines." << endl;
cout << " The longest function is " << list.maxfun << ":" << list.maxlen << " lines." << endl;
cout << " The longest function is " <<"on the " <<list.maxline<<" line"<< endl;
cout << " The maximum function nesting is :" << list.maxh << endl;
cout << " Grade " << res[0] <<" "<< remark(res[0]) << " routine size style." << endl;
cout << " Grade " << res[1] <<" "<< remark(res[1]) << " commenting style." << endl;
cout << " Grade " << res[2] <<" "<< remark(res[2]) << " white space style." << endl;
}
int main()
{
int key;
cout << "---------------------------------------------------------------" << endl;
cout << " 请输入进行分析的程序文件路径及其后缀(可使用相对路径)" << endl;
cout << "---------------------------------------------------------------" << endl;
cout << "分析文件路径:";
getline(cin, name);
ifstream in(name);
//读入文件
system("cls");
if (!in)
{
cout << "----------------------------------------------------------" << endl;
cout << " 文件打开失败 " << endl;
cout << "----------------------------------------------------------" << endl;
Sleep(2000);
system("cls");
main();
}
string str;
while (1)
{
line++;
if (in.eof())
//判断文件读取结束
break;
getline(in, str);
analyze(str);
}
in.close();
show();
cout << "----------------------------------------------------------" << endl;
cout << " 1.分析其他程序 2.退出 " << endl;
cout << "----------------------------------------------------------" << endl;
cout << "请输入序号 : ";
cin >> key;
if (key == 1) {
system("cls"); //清屏
getchar(); //清除缓存
main();
}
return 0;
}
分析文档 :
二、概要设计
1、定义输出表单数据结构体
Struct result{
int Code = 0;
//注释行
int Comments = 0;
//空行
int Blanklines = 0;
int scode = 0;
int scomment = 0;
int sspace = 0;
//函数个数
int countfun = 0;
//函数总长度
int funlen = 0;
//最长函数
int maxlen = 0;
//函数平均长度
double avelen = 0;
//最长函数所在行数
int maxline = 0;
//记录函数名
int maxh = 0;
string funname[max];
//最长函数名
string maxfun;
}
2、实现概要
(1)主函数部分:
Int main()
{
读取C程序;
分析数据;
输出表单。
}
(2)分析部分:
Void analyze(读取数据)
{
If(匹配注释行)
注释行计数
Else if(匹配空行)
空行计数
Else
代码行计数
If(匹配函数定义)
{
函数个数+1
开启函数行计数功能
记录函数名
}
If(匹配函数结尾)
{
关闭函数行技术
寻找最长函数
}
If(函数行计数开关)
{
函数行进行计数;
在该函数中寻找子函数,填入邻接表(数组模拟)
}
}
(3)展示部分:
Void show(读取结构体数据){
对结构体中数据进行分析。
进行评分。
按照题目要求输出程序分析结果。
}
三、详细设计
1.读入C程序:
Ifstream in(C程序相对路径);
if (!in)
{
cout << "文件打开失败" << endl;
return 1;
}
string str; //采用string类存储字符串,便于之后操作
while (1)
{
line++;
if (in.eof()) //判断文件读取结束
break;
getline(in, str); //逐行读取
analyze(str); //进行数据分析
}
2.数据分析:
void analyze(string s)
{
if (regex_match(s, regex("^(\w{2,10}) (\w{1,100})\((.{0,100})\).{0,2}"))) {
//通过正则表达式匹配函数定义
list.countfun++;
//函数个数+1
list.funname[list.countfun] = s;
flag = 1;
//开启计数器
}
if (regex_search(s, regex("\{"))) //通过变量kuohao模拟栈来进行括号匹配
{
kuohao++;
}
if (regex_search(s, regex("\}")))
{
if (kuohao)
kuohao--;
if(kuohao==0&&flag) //当栈空则说明函数结束
{
fun++;
//加上尾花括号
list.funlen++;
if (fun > list.maxlen) //与之前的最长函数比较得到新的最长函数
{
list.maxlen = fun; //存储长度
list.maxfun = list.funname[list.countfun]; //存储最长函数名
list.maxline = line-fun+1; //记录最长行数所在行
}
fun = 0; //函数行数计数器清零
flag = 0; //关闭函数行计数功能
}
}
if (regex_search(s, regex("//"))) //注释行搜索;
list.Comments++;
else if (s == "") //空行搜索
list.Blanklines++;
else {
if (flag) //函数行处理
{ //在当前函数中找子函数建立邻接表
for (int i = 1; i < list.countfun; i++) {
if (strstr(s.c_str(), off(list.funname[i]).c_str()) != NULL) {
graph[list.countfun][++num[list.countfun]] = i; //子函数匹配
}
}
fun++;
list.funlen++;
}
list.Code++; //代码行计数
}
}
3.输出部分:(按照题目版面进行输出)
void show()
{
//计算每个函数的最大镶嵌深度
for (int i = 1; i <= list.countfun; i++) {
dfs(i);
memset(visited, 0, sizeof(visited));
}
list.scode = (int)((double)list.Code / (list.Code + list.Comments + list.Blanklines)*100+0.5);
list.scomment = (int)((double)list.Comments / (list.Code + list.Comments + list.Blanklines) * 100 + 0.5);
list.sspace = (int)((double)list.Blanklines / (list.Code + list.Comments + list.Blanklines) * 100 + 0.5);
list.avelen = (double)list.funlen / list.countfun;
string res = evaluate(list);
cout << "The result of analysing program file "" + name + "":" << endl;
cout << " Lines of code : " << list.Code << endl;
cout << " Lines of comments : " << list.Comments << endl;
cout << " Blank lines : " << list.Blanklines << endl;
cout << " Code Comments Space" << endl;
cout << " ==== ======== =====" << endl;
cout << " " <<list.scode<< "% "<<setw(6)<<list.scomment << "% " << list.sspace<<"%"<< endl;
cout << " The program include " << list.countfun << " functions." << endl;
cout << " The average length of a section of code is " <<list.avelen<< " lines." << endl;
cout << " The longest function is " << list.maxfun << ":" << list.maxlen << " lines." << endl;
cout << " The longest function is " <<"on the " <<list.maxline<<"th line"<< endl;
cout << " The maximum function nesting is :" << list.maxh << endl;
cout << " Grade " << res[0] <<" "<< remark(res[0]) << " routine size style." << endl;
cout << " Grade " << res[1] <<" "<< remark(res[1]) << " commenting style." << endl;
cout << " Grade " << res[2] <<" "<< remark(res[2]) << " white space style." << endl;
}
4.选作完成部分
(1)计算函数的平均
答:开启flag计算函数部分,利用count记录flag开启次数,即函数个数,故
平均长度(avelen)=函数总长度(funlen) / 函数个数(count);
(2)找出最长函数及位置
答:增设两个变量记录最长函数名及其位置,每次关闭flag(即函数结尾),与这两个变量进行比较替换。
(3)找出最大函数镶嵌深度
答:增设一个变量(maxh)表示最大深度,在开启flag的时候,同时寻找子函数,建立邻接表(数组模拟),然后根据深度优先遍历,配合静态变量,最后与maxh进行比较替换。
附:深度优先遍历部分:
void dfs(int i) {
static int count;
visited[i] = 1;
for (int j = 1; j <= num[i]; j++) {
if (!visited[graph[i][j]]) {
count++;
dfs(graph[i][j]);
}
}
if (!num[i]) {
if (count+1>list.maxh) {
list.maxh = count + 1;
}
count = 0;
}
}
四、调试分析
1、这道题目主要是对文件的读取分析操作,将文本的每一行用string存取,通过各自的计数器,记录程序分析的结果。
2、函数的搜索,采用正则表达式进行匹配,搜索到函数定义,开启flag函数计数部分,
通过简易栈((int)kuohao),进行花括号({})匹配,当栈空时函数结束。
3、函数行、代码行和空行的比例采用四舍五入的方法。
4、程序评分中,单独用一个函数(string evaluate)根据数据进行评分后,返回3个字符,再用(string remark)函数对单独的字符给出评语。
5、求函数镶嵌深度,主要使用树的思路,也可以算是有向图吧..,根据建立的邻接表,进行深度优先遍历,再寻找最大深度。
五、用户手册
1.本程序的运行环境为DOS操作系统,执行文件为:程序分析.exe。
2.进入演示程序后,显示文本方式的用户界面:
3.键入程序路径开始程序分析,路径不存在会提示文件打开失败,暂停2s后重新执行主程序。
4.进行分析后输入操作指令可继续分析其他程序。
六、测试结果。
1.分析本程序(main.cpp)
2.分析测试程序(test.cpp)
七、附录
程序头文件:
#include<iostream>
#include<string>
#include<fstream> //文件读取,输出函数
#include<regex> //正则表达式头文件
#include<iomanip>
#include<queue> //STL库中队列
#define max 10000 //给定最大值
using namespace std;