怎樣刪除C/C++代碼中的全部註釋?淺談狀態機的編程思想

K&R習題1-23中,要求「編寫一個程序,刪除C語言程序中全部的註釋語句。要正確處理帶引號的字符串與字符常量。在C語言中,註釋不容許嵌套」html

若是不考慮字符常量和字符串常量,問題確實很簡單。只須要去掉//和/* */的註釋。正則表達式

考慮到字符常量'\''和字符串常量"he\"/*hehe*/",還有相似<secure/_stdio.h>的頭文件路徑符號以及表達式5/3中的除號/,以及狀況就比較複雜了。算法

另外,還有單行註釋//中用\進行折行註釋的蛋疼狀況(這個狀況連不少編輯器高亮都沒考慮到)。編程

 

我想,這種問題最適合用正則表達式來解析,perl之類的語言應當很好處理,問題是這裏讓你用C語言實現,可是C語言對正則表達式並無顯式的支持。數據結構

學過編譯原理的應該知道,正則表達式對應三型文法,也就對應着一個有限狀態自動機(能夠用switch偏重算法來實現,或者用狀態轉換矩陣/表偏重數據結構來實現),app

因此這裏的問題實際上是設計一個狀態機,把輸入的字符流扔進去跑就能夠了。編輯器

那什麼是狀態機呢?K&R第一章怎麼沒有介紹呢?ide

 

【一個簡單的狀態機】測試

先看《K&R》第一章的一個簡單習題1-12:"編寫一個程序,以每行一個單詞的形式打印其輸入"this

 

在這個題目以前,1.5.4節的單詞計數示例中,其實K&R已經展現了一個很是簡單的狀態機。但沒有提到這種編程思想

固然這個題目也能夠狀態機的思想來編程。

回到題目,咱們設初始的狀態state爲OUT,表示當前字符不在單詞中(不是單詞的組成字符),若是當前字符在單詞中(屬於單詞的一部分),則state設爲IN。

顯然字符只能處於上述兩種狀態之一,有了這2個狀態,咱們就能夠藉助狀態來思考問題 ——

(1)當前狀態爲OUT:若當前字符是空白字符(空格、製表符、換行符),則維護當前狀態仍爲OUT;不然改變狀態爲IN。

(2)當前狀態爲IN:若遇到的當前字符是非空白字符,則維護當前狀態爲IN;不然改變狀態爲OUT。

處於不一樣的狀態,根據題意能夠給予相對應的動做——

每當狀態爲IN的時候,意味字符屬於單詞的一部分,輸出當前字符;

而當狀態從IN切換爲OUT的時候,說明一個單詞結束了,此時咱們輸出一個回車符;狀態爲OUT什麼也不輸出

 

能夠看出,藉助自定義的狀態,可使編程思路更加清晰。

在遍歷輸入字符流的時候,程序(機器)就只能處於兩種狀態,對應不一樣狀態或狀態切換能夠有相應的處理動做

這樣的程序不妨稱爲「狀態機」。

 

按照上面的思路,代碼實現就很是簡單了——

 1 #include <stdio.h>
 2 #define OUT 0 /* outside a word */
 3 #define IN 1  /* inside a word  */
 4 
 5 int main(void)
 6 {
 7     int c, state;
 8 
 9     state = OUT;
10     while ( ( c = getchar() ) != EOF ) {
11         if (state == OUT) {
12             if (c == ' ' || c == '\t' || c == '\n')
13                 state = OUT;
14             else {
15                 state = IN;
16                 putchar(c); //action
17             }
18         } else {
19             if (c != ' ' && c != '\t' && c != '\n') {
20                 state = IN;
21                 putchar(c); //action
22             } else {
23                 putchar('\n');//action
24                 state = OUT;
25             }
26         }
27     }
28     return 0;
29 }

 

讓咱們回到主題吧——

【「編寫一個程序,刪除C語言程序中全部的註釋語句。要正確處理帶引號的字符串與字符常量。在C語言中,註釋不容許嵌套」】

按照註釋的各方面規則,咱們來設計一個狀態機——

00)設正常狀態爲0,並初始爲正常狀態

每遍歷一個字符,就依次檢查下列條件,若成立或所有檢查完畢,則回到這裏檢查下一個字符

01)狀態0中遇到/,說明可能會遇到註釋,則進入狀態1          ex. int a = b; /

02)狀態1中遇到*,說明進入多行註釋部分,則進入狀態2         ex. int a= b; /*

03)狀態1中遇到/,說明進入單行註釋部分,則進入狀態4         ex. int a = b; //

04)狀態1中沒有遇到*或/,說明/是路徑符號或除號,則恢復狀態0      ex. <secure/_stdio.h> or 5/3

05)狀態2中遇到*,說明多行註釋可能要結束,則進入狀態3        ex. int a = b; /*heh*

06)狀態2中不是遇到*,說明多行註釋還在繼續,則維持狀態2       ex. int a = b; /*hehe

07)狀態3中遇到/,說明多行註釋要結束,則恢復狀態0          ex. int a = b; /*hehe*/

08)狀態3中不是遇到/,說明多行註釋只是遇到*,還要繼續,則恢復狀態2  ex. int a = b; /*hehe*h

09)狀態4中遇到\,說明可能進入折行註釋部分,則進入狀態9       ex. int a = b; //hehe\

10)狀態9中遇到\,說明可能進入折行註釋部分,則維護狀態9       ex. int a = b; //hehe\\\

11)狀態9中遇到其它字符,則說明進入了折行註釋部分,則恢復狀態4    ex. int a = b; // hehe\a or hehe\<enter>

12)狀態4中遇到回車符\n,說明單行註釋結束,則恢復狀態0        ex. int a = b; //hehe<enter>

13)狀態0中遇到',說明進入字符常量中,則進入狀態5           ex. char a = '

14)狀態5中遇到\,說明遇到轉義字符,則進入狀態6           ex. char a = '\

15)狀態6中遇到任何字符,都恢復狀態5                 ex. char a = '\n 還有如'\t', '\'', '\\' 等 主要是防止'\'',誤覺得結束

16)狀態5中遇到',說明字符常量結束,則進入狀態0            ex. char a = '\n'

17)狀態0中遇到",說明進入字符串常量中,則進入狀態7         ex. char s[] = "

18)狀態7中遇到\,說明遇到轉義字符,則進入狀態8           ex. char s[] = "\

19)狀態8中遇到任何字符,都恢復狀態7                 ex. char s[] = "\n 主要是防止"\",誤覺得結束

20)狀態7中遇到"字符,說明字符串常量結束,則恢復狀態0        ex. char s[] = "\"hehe"

前面說過,不一樣狀態能夠有相應的動做。好比狀態0、五、六、七、8都須要輸出當前字符,再考慮一些特殊狀況就能夠了。

讀者實現時能夠藉助debug宏定義,將測試語句輸出到標準錯誤輸出,須要時能夠重定位到標準輸出,即2>&1,而後經過重定向|到more進行查看。

 

上面的狀態機涉及到了[0, 9]一共10種狀態,對應的狀態轉換圖(或者說狀態機/自動機)以下:

 

有了這些狀態表示,編寫代碼就很容易了——

 1 /*
 2  *Copyright (C) Zhang Haiba
 3  *Date 2014-02-26
 4  *File exercise1_23.c
 5  *
 6  *this program removes all comments in grammatical C code,
 7  *and as a integrity solution for exercise1-23 in <<K&R>>
 8  */
 9 
10 #include <stdio.h>
11 #define debug
12 //#define debug(fmt, args...) fprintf(stderr, fmt, ##args)
13 
14 void dfa();
15 
16 int main(void)
17 {
18     dfa();
19     return 0;
20 }
21 
22 void dfa()
23 {
24     int c, state;
25 
26     state = 0;
27     while ((c = getchar()) != EOF) {
28         if (state == 0 && c == '/')         // ex. [/]
29             state = 1;
30         else if (state == 1 && c == '*')     // ex. [/*]
31             state = 2;
32         else if (state == 1 && c == '/')    // ex. [//]
33             state = 4;
34         else if (state == 1) {                // ex. [<secure/_stdio.h> or 5/3]
35             putchar('/');
36             state = 0;
37         }
38 
39         else if (state == 2 && c == '*')    // ex. [/*he*]
40             state = 3;
41         else if (state == 2)                // ex. [/*heh]
42             state = 2;
43 
44         else if (state == 3 && c == '/')    // ex. [/*heh*/]
45             state = 0;
46         else if (state == 3)                // ex. [/*heh*e]
47             state = 2;
48 
49         else if (state == 4 && c == '\\')    // ex. [//hehe\]
50             state = 9;
51         else if (state == 9 && c == '\\')    // ex. [//hehe\\\\\]
52             state = 9;
53         else if (state == 9)                // ex. [//hehe\<enter> or //hehe\a]
54             state = 4;
55         else if (state == 4 && c == '\n')    // ex. [//hehe<enter>]
56             state = 0;
57 
58         else if (state == 0 && c == '\'')     // ex. [']
59             state = 5;
60         else if (state == 5 && c == '\\')     // ex. ['\]
61             state = 6;
62         else if (state == 6)                // ex. ['\n or '\' or '\t etc.]
63             state = 5;
64         else if (state == 5 && c == '\'')    // ex. ['\n' or '\'' or '\t' ect.]
65             state = 0;
66 
67         else if (state == 0 && c == '\"')    // ex. ["]
68             state = 7;
69         else if (state == 7 && c == '\\')     // ex. ["\]
70             state = 8;
71         else if (state == 8)                // ex. ["\n or "\" or "\t ect.]
72             state = 7;
73         else if (state == 7 && c == '\"')    // ex. ["\n" or "\"" or "\t" ect.]
74             state = 0;
75 
76         //debug("c = %c, state = %d\n", c, state);
77 
78         if ((state == 0 && c != '/') || state == 5 || state == 6 || state == 7 || state == 8)
79             putchar(c);
80     }
81 }

 

【測試用例(1)a.out < test.c > test2.c】

 

test.c以下:

 1 /*
 2  *This code make no sense(Compiled successfully), 
 3  *but for exercise1_23 in <<K&R>> to test remove all comments in C code.
 4  */
 5 
 6 #          include         <stdio.h>
 7 #  include  <secure/_stdio.h>
 8 #include      "/Users/apple/blog/zhanghaiba/KandR/test.h"
 9 #define CHAR '\'' /*/test/*/
10 #  define LESS(i) ( ((i) << 31) / 2 )
11 #        define STRING "\"string\"" //to ensure legal
12 
13 int main(void)
14 {
15     int w; // \a
16     int x;/*hehe*/
17     double y; // \ 
18     double z; // \b \\\\
19     int none;
20 
21     ///*testing*/
22     int idx;
23     if (idx > 3 && idx < 6) idx = idx/100; //go and \
24     con_tinue\
25     hehe
26 
27     /* // */    
28     char a = '/';    // /
29     char b = '*';    // *
30     char c = '\'';    // '
31     char d = '\n';    // enter
32     char e = '\"';    // "    
33     char f = '\\';    // \
34     char g = '<';    // <
35     char h = '>';    // >
36     char i = '"';    // "
37 
38     /* special***string */
39     char special0[] = "\"hello, world!\"";
40     char special1[] = "//test";
41     char special2[] = "he\"/*hehe*/";
42     char *special = " \' hi \0\b\t \\\\ \a\e\f\n\r\v wolegequ \\ ";
43     return        0;
44 }

 

測試截圖對好比下:

(說明:因爲編輯器高亮的緣由,注意//後面加字符\的折行註釋部分顏色其實不對的,另外17行\後面是有一個空格的)

 

【測試用例(2)./a.out < ~/open_src_code/glibc-2.17/malloc/malloc.c > test2.c】

glibc-2.17源碼中的的malloc.c包括空行和註釋有5163行,通過上面去註釋後代碼變成3625行。

測試發現去註釋成功。這裏不可能貼對比代碼了。

有興趣的讀者可自行測試。

歡迎提供測試不正確的用例代碼。

 

@Author: 張海拔

@Update: 2014-2-26

@Link: http://www.cnblogs.com/zhanghaiba/p/3569928.html

相關文章
相關標籤/搜索