最近老师给了一道题啊...叫我们写一个扫描器,让它可以去除空白,然后可以去除注释,把结果导入另外的文件里啊....
答:空白就检查吧。注释用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;
- }