最近老師給了一道題啊...叫我們寫一個掃描器,讓它可以去除空白,然後可以去除註釋,把結果導入另外的文件裏啊....
答:空白就檢查吧。註釋用stack匹配/**/檢查//去行
去掉C/C++程序代碼中的註釋
C知識點 2009-12-27 17:36:04 閱讀106 評論0 字號:大中小
程序員面試寶典的題目解析方法基本上沒什麼問題,但是答案中有很多紕漏,甚至有些事錯誤的,這是很多有一定編程經驗的程序員看過那本書得出的共同結論。
有錯誤不怕,我們學習的人一定要懂,畢竟那不是教材嘛,自己要把c基礎打好,上面有很多經典問題供我們思考,而且會對我們面試有很大好處。不過讀者一定要相信,這本書不可能提高你的C基礎水平。
下面是第45頁的一個題目,其中有一處錯誤,我將那句話註釋掉了,寫了一句正確的代碼,且給了原因分析,編譯通過,可以去掉程序代碼中的註釋語句。如果還有不嚴謹的部分,盼指爲幸!
===============================================================================
下面的代碼比較複雜一些,請分段思考。
另外要聯繫思考:在""和''中間的雙斜槓中第一個斜槓在"和'後面判斷就跳過了,第二次是讓case語句來處理,然而case的第一個if就是依靠前面是否已經探測到過"或',探測到了的話,要跳過。
還要考慮到非註釋中的單引號''中間只能有一個/,否則C語句非法。
===============================================================================
/********************************************************
功能:去除C/C++中的註釋
輸入:指向C/C++程序代碼的指針
來源:程序員面試寶典第45頁
注意:①要考慮到""或' '中的//和/*,//和/*的嵌套關係。
②單引號、雙引號中的//是兩個字符,第一個字符在單引號的case語句中跳過了,
第二個字符則在case '/'中處理。
*********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
void remove_comment(char *buf, size_t size)
{
char *p, *end, c; //p-動態移動的字符指針,end-指向文件末尾的字符指針,c-存儲沒一個p指向的字符
char *sq_start, *dq_start; //sq_start-單引號開始位置(single),dq_start-雙引號開始(double)
char *lc_start, *bc_start; //lc_start-//的開始位置,bc_start-/*的開始位置
size_t len; //記錄某符號結束和開始的位置之差(長度,偏移量)
p = buf;
end = p + size;
sq_start = dq_start = NULL;
lc_start = bc_start = NULL;
while (p < end) /*當指針沒有到達文件末尾 /r///***///*/,故意帶這些不規範的符號的,因爲調試就用這個代碼,哈哈。*/
{
c = *p; //用字符變量c存儲指針指向的字符
switch (c) //根據c的值做相應處理
{
case '/'': /*處理單引號,其實只是爲了排除'//'的情況,否則不需要有這個情況判斷*/
{
if (dq_start || lc_start || bc_start) //當遇到過雙引號、//或/*的時候,則不需要再判斷'//'的情況了。
{
p++;
continue; //繼續下一個,對while而言的
}
/*******************************以下是沒有遇到過雙引號或//或/*的時候*******************************/
if (sq_start == NULL) /****否則:如果未遇到單引號****/
{
sq_start = p++; //start指向單引號的開始位置,p指向下一個(分兩句理解)
}
else /*如果遇到過單引號,sq_start指向單引號開始位置*/
{
len = (p++) -sq_start; //len = p-sq_start; p++;
if (len == 2 && *(sq_start+1) == '//') //這個是將遇到'//'的情況排除
{
continue; //忽略單引號中單獨存在//的時候,即不再往下處理。
}
sq_start = NULL; //否則將sq_start置位爲NULL,C語言中單引號內最多隻可能有一個字符而已,不要思考複雜了哦len == 0 或 1 或 2
}
/*******************************以上是沒有遇到過雙引號或//或/*的時候*******************************/
break;
}
case '/"': /*處理雙引號,其實只也是爲了排除"//"的情況,否則不需要有這個情況判斷,注意第二個斜槓不在這裏判斷*/
{
if (sq_start || lc_start || bc_start) //當遇到過單引號、//或/*的時候,則不需要處理
{
p++;
continue;
}
/*****************以下是沒有遇到過單引號或//或/*的時候*****************/
if (dq_start == NULL) /*如果沒有遇到過雙引號*/
{
dq_start = p++; //標記遇到了雙引號
}
else if (*((p++) -1) =='//') //雙引號中的/也不需要處理。
{
continue;
}
printf("hello // world?? /**/"); //這種情況呢?怎麼辦?——這個情況會在遇到/是的第一個if語句被跳過
dq_start = NULL; //如果雙引號中不是//,標記爲NULL
/*****************以上是沒有遇到過單引號或//或/*的時候*****************/
}
case '/': //斜槓,注意這個斜槓也可以是'//',"//",//,/*/中的第二個斜槓,但會在下面第二行代碼中被忽略掉
{
if (sq_start || dq_start || lc_start || bc_start) //如果是單引號、雙引號、斜槓、/*的後面
{
p++;
continue;
}
/***********************下面是遇到註釋//或/*的時候****************************/
c = *(p + 1); //否則c取p指向字符的下一個字符
if (c == '/') //遇到了雙斜槓
{
lc_start = p; //標記雙斜槓的開始
p += 2; //p指向雙斜槓後面的字符
}
else if (c == '*') //遇到了/*
{
bc_start = p; //標記/*的開始
p += 2; //p指向/*後面的字符
}
/*************************上面是遇到註釋//或/*的時候**************************/
else
{ //其它情況,再去判斷下一個是什麼符號——注意:C程序可以有其他情況嗎?這句話我認爲永遠不可能執行到。
p++;
}
}
case '*': //星號,同斜槓,但少了如果遇到/*的情況,因爲遇到這種情況後,要判斷是不是遇到結束的地方*/了
{
if (sq_start || dq_start || lc_start) //如果是單引號、雙引號、斜槓、/*的後面
{
p++;
continue;
}
if (*(p + 1) != '/') //如果星號後面緊跟的不是斜槓,那麼忽略過。
{
p++;
continue;
}
p += 2; //否則p指向斜槓後面那個字符。注意下面的清空語句,p指向的那個字符並不會被清除。
memset(bc_start, ' ', p-bc_start); //清空/* …… */中間的內容包括註釋符號本身。
bc_start = NULL;
break;
}
case '/n': /*換行符,主要處理遇到雙斜槓時,需要清除雙斜槓到/n的前面的字符*/
{
if (lc_start == NULL) //如果還沒有遇到雙斜槓,那麼忽略
{
p++;
continue; /*這兩行本程序每次case後面都緊跟,就是忽略過的意思*/
}
c = *(p - 1);
//如果遇到過雙斜槓,清空雙斜槓本身和到n前面的那個字符,p指向下一個字符,/r是回車符(光標退回到最前面),要忽略。有這個情況嗎???
memset(lc_start, ' ', (c == '/r'? ((p++) -1) : p++) - lc_start);
lc_start = NULL;
break;
}
default:
p++;
break;
}
/****************************************************
如果遇到雙斜槓,這個if語句存在的意義在於萬一最後
一行代碼是帶有雙斜槓但沒有給換行符/n的,也要清除掉。
不帶文件末尾的雙斜槓的行尾一定有/n,這不是代碼中寫的
/n而是我們的回車鍵換行操作寫入文件的。
*****************************************************/
if (lc_start)
{
memset(lc_start, ' ', p - lc_start);
}
}
}
/**********************************************
main函數的開始
***********************************************/
int main (int argc, char *argv[])
{
int fd, n;
char buf[102400];
if (argc != 2)
{
printf("command error: Input as ./command <file>/n");
}
fd = open(argv[1], O_RDONLY); /*只讀打開*/
if (fd == -1)
{
return -1;
}
n = read(fd, buf, sizeof(buf));
if (n == -1 || n == 0)
{
close(fd);
return -1;
}
printf("test/n");
remove_comment(buf, n);
*(buf + n) = '/0';
printf("%s", buf);
close(fd);
return 0;
}
二叉樹——根據遍歷結果,畫出對應的二叉樹
數據結構 2009-12-26 12:24:49 閱讀79 評論0 字號:大中小
這道題目很經典,具體如下:
已知遍歷結果如下,試畫出對應的二叉樹:
前序:A B C E H F I J D G K
中序:A H E C I F J B D K G
解題要點:
1、前序、中序、後序——都針對中間那個節點而言(根節點也是中間的節點)。
前序,指先遍歷中間節點,然後左,然後右。
中序,指左——中——右。
後序,指右——中——左。
2、根據兩種不同序列的遍歷方法,便可畫出二叉樹。
解題答案如下:(對照着看會好理解這道題目一些的)
解題思路:
1、前序中序都首先找出A,推斷出:A沒有左孩子(否則中序要先遍歷其左孩子)。A爲根節點(前序先遍歷中節點)。
2、A沒有左孩子,一定就只有右孩子了(否則哪來那麼多後面的字符)。右孩子一定是B,根據前序結果(前序先遍歷中節點)。
3、中序的第二個不是B,說明B一定有左孩子(中序先遍歷左孩子)。既然有左孩子,那麼在前序的B後面那位一定是啦——C,C有左孩子嗎?一定有,否則中序的A後面就跟C啦。那麼前序的C後面的E一定是C的左孩子(還是深入講一下:前序是先遍歷左,但左還有左的時候,得先遍歷左的左,一直深入下去,找到最終的那個左節點,這個一定要明白,否則此題無法解開)。
4、同理:E也有左孩子,就是前序的E後面那位:H!好,既然中序的A後面是H,H一定沒有左孩子啦。
5、下面怎麼辦呢?前序:H後面是F,中序:H後面的E、C已經確定了位置了,再後面是I,怎麼辦?
6、注意:中序H後面是E,說明H沒有右孩子啊!(中序遍歷方法!!)同理,E後面是C說明E也沒有右孩子,C後面呢,是B嗎?如果是,那麼C也沒有,驚喜了吧,中序當中C後面是I,說明C有右孩子,是誰呢?
7、既然C後面有右孩子,那麼在前序中H後面的F一定就是我們要找的右孩子啦(因爲前序是找中節點,然後找左孩子,左孩子中又先找中間節點,再找左孩子的左孩子……中——左——右,嵌套思想)。
8、F確定好位置後,那麼考慮F有左孩子嗎?如果沒有,中序的C後面應該跟F,但是中序的C後面跟I,說明F有左節點,那麼前序找完F一定要找到這個左節點了,就是說前序的F後面的I一定是F的左節點。
9、I有左孩子嗎?中序的C後面跟的是I,說明I就是最終的左孩子了。
10、F有右孩子嗎?如果沒有,中序I的下一位應該是B,可是並非,所以它有右孩子,那麼前序的I後面的J一定是這個右孩子。
11、J有左孩子嗎?如果有,中序的F後面不能是J,可是並非如此,所以沒有了。J有右孩子嗎?如果有,中序的已經找過J啦,下一位就是找右孩子,所以看中序的J後面那一位,發現是B,B不是已經確定好位置了嗎,所以啊,沒有啦。回到B節點了。
12、前序的J後面的D一定就是B的右孩子(因爲回到B了嘛,前序要先把B的右孩子當中節點找到,再去找右孩子的左孩子,再把左孩子當中節點找到……嵌套啊)。
13、D有左孩子嗎?如果有,中序的B後面就不可能是D(因爲中序要先找到最最最最最左邊的那個啊)。所以沒有。既然沒有,那麼前序就要找D的右孩子啦,那就是前序的D後面那個G。
14、G有左孩子嗎?如果沒有的話,中序的D後面一定是G啦,可是不是,所以它有左孩子,這個左孩子就是前序在找到G後面該找到的那位了,就是K。
OK,二叉樹的圖畫出來了。
總結二叉樹查找關鍵點:
1、每次都要假設,假設了立即推翻,如果假設有,發現不能成立的話,就推翻了這個假設,即確定了一個節點的存在與否。
2、一定要明白前序、中序、後序的遍歷方法,要透徹的領悟到嵌套的思想,比如找左孩子,結果找到的左孩子還有左孩子,那麼要先找左孩子的左孩子,如果左孩子的左孩子還有左孩子,那麼……以此類推。
3、假設一定就一次,便可深入,否則就帶有一定的猜測,然後證實的成分了,容易暈掉。
4、每次確定好一個位置都是100%確定的,所以畫出來,不要猶豫,然後在前序、中序上做相應標記,知道自己下一個該確定那個字符的位置了。
5、解這個題目,一定不能着急,心要穩,別深入,沒有更好的深入的方法(我沒找到而已,哈哈)。每次找到一個節點就直接問,他有左孩子嗎?如果有,那麼在中序的查找中一定就該找他了,結果發現不是,所以推翻。如果沒有,那麼在前序一定就該找到這個節點了。方法就這一個而已,可以解開題目。
6、這個方法對後序查找也生效。
數據結構經典問題——出棧順序
數據結構 2009-12-26 11:28:59 閱讀109 評論2 字號:大中小
對於數據結構的問題,如果思路稍有不對,就容易陷入邏輯混亂。我希望自己對數據結構的理解,能夠給大家一點幫助。我會將所有我有過心得的問題在我的博客上寫出來,歡迎大家瀏覽,如果有什麼不對的地方,還請大家指正,有問題可以給我留言,我會盡量解決,謝謝。
聲明一下我寫博客的初衷:不是炫耀,而是回報。因爲我在計算機方面的知識好多都從網上找到答案,因此我也
將自己搜尋整理的材料,自己寫的材料,展示到網上,算是盡一份力吧。
一個經典問題如下(不願意看思路的可以直接看紅色字體部分):
一個棧的入棧序列是a,b,c,d,e則棧的不可能的輸出序列是:()
A edcbd B decba C dceab D abcde
棧之根本——先進後出(first in,lastout)初次接觸到這個問題的人,或許會認爲入棧abcde,所以出棧只能是edcba所以BCD都不對。
其實是這個問題描述有歧義,應該是分段入棧的順序,也就是說,可能先入棧a,取出a,入棧b,取出b……,所以D也是可能的。
知道這個意思了以後,就要明確這個問題的矛盾根本所在:第一次出棧d,說明什麼?說明a,b,c一定早已入棧(入棧順序決定的)。那麼在出棧d以後,a,b,c的出棧順序一定是c,b,a,而不用理會中間穿插着出棧了d後面的字符(因爲可以再入棧,再出棧嘛)。所以立即選中C,不用猶豫,理由簡單:d出棧了,abc一定已經入棧,那麼abc只能以cba的順序出棧,C不符合,OK!This problem is so esay, Thanks for my teacher Wang Shanshan.
數據結構中的思路一定要單一,要簡明,抓住問題根本,萬不可考慮過多,試探法只有在不知道解題思路的情況下去嘗試,嘗試多了,你會發現,邏輯容易混亂,俗稱“我暈”!
去除C/C++中的註釋
文章分類:C++編程
- /********************************************************
- 功能:去除C/C++中的註釋
- 輸入:指向C/C++程序代碼的指針
- 來源:程序員面試寶典第45頁
- 注意:①要考慮到""或' '中的//和/*,//和/*的嵌套關係。
- ②單引號、雙引號中的//是兩個字符,第一個字符在單引號的case語句中跳過了,
- 第二個字符則在case '/'中處理。
- *********************************************************/
- #include <stdio.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- void remove_comment(char *buf, size_t size)
- {
- char *p, *end, c; //p-動態移動的字符指針,end-指向文件末尾的字符指針,c-存儲沒一個p指向的字符
- char *sq_start, *dq_start; //sq_start-單引號開始位置(single),dq_start-雙引號開始(double)
- char *lc_start, *bc_start; //lc_start-//的開始位置,bc_start-/*的開始位置
- size_t len; //記錄某符號結束和開始的位置之差(長度,偏移量)
- p = buf;
- end = p + size;
- sq_start = dq_start = NULL;
- lc_start = bc_start = NULL;
- while (p < end) /*當指針沒有到達文件末尾 /r///***///*/,故意帶這些不規範的符號的,因爲調試就用這個代碼,哈哈。*/
- {
- c = *p; //用字符變量c存儲指針指向的字符
- switch (c) //根據c的值做相應處理
- {
- case '/'': /*處理單引號,其實只是爲了排除'//'的情況,否則不需要有這個情況判斷*/
- {
- if (dq_start || lc_start || bc_start) //當遇到過雙引號、//或/*的時候,則不需要再判斷'//'的情況了。
- {
- p++;
- continue; //繼續下一個,對while而言的
- }
- /*******************************以下是沒有遇到過雙引號或//或/*的時候*******************************/
- if (sq_start == NULL) /****否則:如果未遇到單引號****/
- {
- sq_start = p++; //start指向單引號的開始位置,p指向下一個(分兩句理解)
- }
- else /*如果遇到過單引號,sq_start指向單引號開始位置*/
- {
- len = (p++) -sq_start; //len = p-sq_start; p++;
- if (len == 2 && *(sq_start+1) == '//') //這個是將遇到'//'的情況排除
- {
- continue; //忽略單引號中單獨存在//的時候,即不再往下處理。
- }
- sq_start = NULL; //否則將sq_start置位爲NULL,C語言中單引號內最多隻可能有一個字符而已,不要思考複雜了哦len == 0 或 1 或 2
- }
- /*******************************以上是沒有遇到過雙引號或//或/*的時候*******************************/
- break;
- }
- case '/"': /*處理雙引號,其實只也是爲了排除"//"的情況,否則不需要有這個情況判斷,注意第二個斜槓不在這裏判斷*/
- {
- if (sq_start || lc_start || bc_start) //當遇到過單引號、//或/*的時候,則不需要處理
- {
- p++;
- continue;
- }
- /*****************以下是沒有遇到過單引號或//或/*的時候*****************/
- if (dq_start == NULL) /*如果沒有遇到過雙引號*/
- {
- dq_start = p++; //標記遇到了雙引號
- }
- else if (*((p++) -1) =='//') //雙引號中的/也不需要處理。
- {
- continue;
- }
- printf("hello // world?? /**/"); //這種情況呢?怎麼辦?——這個情況會在遇到/是的第一個if語句被跳過
- dq_start = NULL; //如果雙引號中不是//,標記爲NULL
- /*****************以上是沒有遇到過單引號或//或/*的時候*****************/
- }
- case '/': //斜槓,注意這個斜槓也可以是'//',"//",//,/*/中的第二個斜槓,但會在下面第二行代碼中被忽略掉
- {
- if (sq_start || dq_start || lc_start || bc_start) //如果是單引號、雙引號、斜槓、/*的後面
- {
- p++;
- continue;
- }
- /***********************下面是遇到註釋//或/*的時候****************************/
- c = *(p + 1); //否則c取p指向字符的下一個字符
- if (c == '/') //遇到了雙斜槓
- {
- lc_start = p; //標記雙斜槓的開始
- p += 2; //p指向雙斜槓後面的字符
- }
- else if (c == '*') //遇到了/*
- {
- bc_start = p; //標記/*的開始
- p += 2; //p指向/*後面的字符
- }
- /*************************上面是遇到註釋//或/*的時候**************************/
- else
- { //其它情況,再去判斷下一個是什麼符號——注意:C程序可以有其他情況嗎?這句話我認爲永遠不可能執行到。
- p++;
- }
- }
- case '*': //星號,同斜槓,但少了如果遇到/*的情況,因爲遇到這種情況後,要判斷是不是遇到結束的地方*/了
- {
- if (sq_start || dq_start || lc_start) //如果是單引號、雙引號、斜槓、/*的後面
- {
- p++;
- continue;
- }
- if (*(p + 1) != '/') //如果星號後面緊跟的不是斜槓,那麼忽略過。
- {
- p++;
- continue;
- }
- p += 2; //否則p指向斜槓後面那個字符。注意下面的清空語句,p指向的那個字符並不會被清除。
- memset(bc_start, ' ', p-bc_start); //清空/* …… */中間的內容包括註釋符號本身。
- bc_start = NULL;
- break;
- }
- case '/n': /*換行符,主要處理遇到雙斜槓時,需要清除雙斜槓到/n的前面的字符*/
- {
- if (lc_start == NULL) //如果還沒有遇到雙斜槓,那麼忽略
- {
- p++;
- continue; /*這兩行本程序每次case後面都緊跟,就是忽略過的意思*/
- }
- c = *(p - 1);
- //如果遇到過雙斜槓,清空雙斜槓本身和到n前面的那個字符,p指向下一個字符,/r是回車符(光標退回到最前面),要忽略。有這個情況嗎???
- memset(lc_start, ' ', (c == '/r'? ((p++) -1) : p++) - lc_start);
- lc_start = NULL;
- break;
- }
- default:
- p++;
- break;
- }
- /****************************************************
- 如果遇到雙斜槓,這個if語句存在的意義在於萬一最後
- 一行代碼是帶有雙斜槓但沒有給換行符/n的,也要清除掉。
- 不帶文件末尾的雙斜槓的行尾一定有/n,這不是代碼中寫的
- /n而是我們的回車鍵換行操作寫入文件的。
- *****************************************************/
- if (lc_start)
- {
- memset(lc_start, ' ', p - lc_start);
- }
- }
- }
- /**********************************************
- main函數的開始
- ***********************************************/
- int main (int argc, char *argv[])
- {
- int fd, n;
- char buf[102400];
- if (argc != 2)
- {
- printf("command error: Input as ./command <file>/n");
- }
- fd = open(argv[1], O_RDONLY); /*只讀打開*/
- if (fd == -1)
- {
- return -1;
- }
- n = read(fd, buf, sizeof(buf));
- if (n == -1 || n == 0)
- {
- close(fd);
- return -1;
- }
- printf("test/n");
- remove_comment(buf, n);
- *(buf + n) = '/0';
- printf("%s", buf);
- close(fd);
- return 0;
- }