问题来源,在14年的暑假的一次小项目当中遇到了一个这样的问题,要求统计C++代码的注释行数,有效代码行数,代码注释公共行数,以及函数个数。
下面稍微解释一下问题,
1)注释行数:指有注释的行,包括有代码和注释的公共行(如:3,4,15,22...)
2)有效代码行:指有代码的行,包括有代码和注释的公共行(如:1,4,11,15,25....)
3)代码注释公共行:指又有代码又有注释的行(如:4,15...)
4)函数个数:这个不用说明了吧。
以下为注释情况展示代码:
复制代码
1 #include <stdio.h>
2
3 //follow is a common line
4 void swap(char *p/* = NULL */)
5 {
6 printf("this is a function /*is not comments*/");
7 }
8
9 int main(void)
10 {
11 int a = 10;
12 int b;
13 char *p = NULL;
14
15 swap(p); //common line
16 if (10 == a)
17 {
18 printf("is not function;//is not comments");
19 }
20 //pure comments/*no use*/
21
22 /*a = 5;
23 printf("not use\n");*/
24
25 b = 3;/*i'm a comment*/ a = 5; /*comments line
26 pure //comments line;"*/......"look there*/
27 printf("test \" escape char");
28 //and the follow,maybe affect the count of function
29 if (*p == '{')
30 {
31 printf("the '{' is a question\n");
32 }
33 //
34
35 return 0;
36 }
复制代码
上面的这个代码片段,已经基本上展示了各种注释可能出现的情况,接着我们来分析一下注释出现的位置:
一、“//”双斜杠注释
1)可能出现在行头部,如:第3行;
2)可能出现在行末尾,如:第15行;
注意:第18行,第26行。第18行中,//位于双引号中,注释失效;第26行中,//位于/**/注释中,//失效。
二、“/**/”斜杠星注释
1)可能出现在函数参数里,如:第4行;
2)可能注释一个段落,如:22,23行;
3)也可能出现在代码中部,如25,26行那样的复杂注释;
注意:第20行,第26行。第20行,/**/位于//注释中,失效。第26行,有一个*/位于“”中,*/失效。
好了,位置分析完毕,下面分析一下如何设计算法:
三、具体算法设计思路
1)解决文件读取
这里我们用c++文件读取,每次读取一行,将读取结果放到string变量里面,这样string变量尾部自动为'\0'。我们用于判断行尾部。
2)双引号问题
由于双引号里面的内容都是无效的,所以我们可以直接来个过滤双引号里面的内容。(具体实现见代码)
3)单引号问题
上面的展示代码里面也看到了,单引号里面的内容(如:if (ch == '{'))可能会影响函数个数的统计,所以我们需要过滤单引号。
4)空行
这个最简单,如果string为空就是空行。包括(\t\n\r' '等等,无效字符,都算空行)。
5)“//”注释
这个较为简单,遇到//就可以进行统计,同时该行也不需要继续遍历了,直接break;然后读取下一行。
6)“/**/”注释
较麻烦,我们这里用到了代码标记,注释标记,同时还设置了当遍历到行最尾部的时候,才进行行数统计,这样避免了一行被统计多次。(具体实现见代码)
7)函数个数
首先说一下统计思路,我们统计函数的时候,只是以大括号({})为统计标记。用栈来表示,遇到左括号,入栈;遇到右括号,弹出一个左括号。知道弹到栈空,函数个数+1,
这样的话,就实现了只保留最外层那对{},里层的括号全部抵销。我们又想了一下,简化了一下,不用栈,直接用一个变量来表示括号个数,遇到左括号,++top;遇到右括号--top,直到top==0,也就相当于栈空,函数个数+1。
其实如下代码的函数个数统计还有问题:
示例:对于类、结构体、枚举体、全局数组,会被统计为一个函数。也就是说,那些在函数体外面的,但是又带有大括号({})的代码,都会被识别为函数。
以下为实现代码:
复制代码
1 #include <iostream>
2 #include <fstream>
3 #include <string>
4 using namespace std;
5
6 int main(void)
7 {
8 string line="";
9 ifstream ifs;
10 ifs.open("Test.cpp");
11 if (!ifs)
12 {
13 cout<<"文件打开失败"<<endl;
14 exit(0);
15 }
16 //////////////////////////////////////////////
17 //标记:双引号 斜杠星 函数 代码 注释
18 int bSyh = 0, bXgx = 0, bHs = -1, bCode = 0, bZs = 0;
19 // "" '' // /* {}
20 /////////////////////////////////////////////
21 //个数:空行 注释 代码 公共 函数
22 int i,nKh = 0, nZs = 0, nDm = 0, nGg = 0, nHs = 0;
23 //
24 while (!ifs.eof())
25 {
26 i = 0;
27 getline(ifs,line); //读取一行文件
28 bCode = 0; //该行没有代码
29 bZs = 0; //该行没有注释
30 if (bXgx) //bXgx 斜杠星注释标记
31 bZs = 1; //该行有注释
32 //过滤无效符号
33 while (line[i] == ' ' || line[i] == '\t' || line[i] == '\r' || line[i] == '\n')
34 {
35 ++i;
36 }
37 //“以下为空行统计区域:开始”
38 if (!bXgx && line[i] == '\0') //空行
39 {
40 ++nKh;
41 continue;
42 }
43 //“空行统计:结束”
44 while (1)
45 {
46 //第一次遇到双引号 引号为非转义字符(\")
47 if (!bSyh && line[i] == '\"' && ((i > 0 && line[i-1] != '\\') || (i == 0)))
48 {
49 ++i;
50 bSyh = 1;
51 continue;
52 }
53 //“正在进行双引号屏蔽....”
54 if (bSyh)
55 {
56 //“ \”结束”
57 if (line[i] == '\"' && ((i > 0 && line[i-1] != '\\') || (i == 0)))
58 {
59 bSyh = 0;
60 }
61 else if (line[i] == '\0') //行末尾
62 {
63 if (bZs)
64 ++nZs;
65 if (bCode)
66 ++nDm;
67 if (bZs && bCode)
68 ++nGg;
69 break;
70 }
71 ++i;
72 continue;
73 }
74 //遇到单引号(避免'{','}'),且非转义字符\',连续跳过3个(第二个'后位置)
75 if (line[i] == '\'' && ((i > 0 && line[i-1] != '\\') || (i == 0)))
76 {
77 i += 3;
78 continue;
79 }
80 //“//注释行”
81 if (!bXgx && line[i] == '/' && line[i+1] == '/')
82 {
83 if (bCode) //“前有代码,混合注释行”
84 {
85 ++nZs; //注释
86 ++nDm; //代码
87 ++nGg; //公共
88 }
89 else //纯注释行
90 {
91 ++nZs;
92 }
93 break; //跳出当前行(即,内while循环),“//”后代码不做判断
94 }
95 //“/*注释开始”
96 if (!bXgx && line[i] == '/' && line[i+1] == '*')
97 {
98 i += 2; //跳过/*符号
99 bXgx = 1; //标记“/*”开始
100 bZs = 1; //“发现注释”
101 continue;
102 }
103 //“正在进行多行注释....”
104 if (bXgx)
105 {
106 //“*/注释结束”
107 if (line[i] == '*' && line[i+1] == '/')
108 {
109 ++i; //“跳过*/”注意后有一个 ++i;
110 bXgx = 0;
111 }
112 else if (line[i] == '\0') //行末尾
113 {
114 if (bCode) //注释前有代码,即“混合行”
115 {
116 ++nDm;
117 ++nZs;
118 ++nGg;
119 }
120 else
121 {
122 ++nZs; //“纯注释”
123 }
124 break;
125 }
126 ++i;
127 continue;
128 }
129 if (line[i] == '\0')
130 {
131 if (bZs)
132 ++nZs;
133 if (bCode)
134 ++nDm;
135 if (bZs && bCode)
136 ++nGg;
137 break;
138 }
139 //“以下全是有效代码区域”
140 //“函数个数统计区域:开始”
141 if (line[i] == '{') //记录函数左括号
142 {
143 ++bHs;
144 }
145 else if (line[i] == '}') //遇到函数右括号
146 {
147 if (bHs == 0) //“发现一个函数”
148 ++nHs;
149 --bHs;
150 }
151 //“函数统计:结束”
152 ++i;
153 bCode = 1; //能执行到这里,说明该行存在代码
154 }
155 }
156
157 cout<<"注释: "<<nZs<<endl;
158 cout<<"代码: "<<nDm<<endl;
159 cout<<"空行: "<<nKh<<endl;
160 cout<<"公共: "<<nGg<<endl;
161 cout<<"函数: "<<nHs<<endl;
162
163 return 0;
164 }