問題來源,在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 }