K&R The C programming language1

P13  编写一个将输入复制到输出的程序,并将其中连续的多个空格用一个空格代替。

第一种方法利用进入c==' '的条件后,再进行c=getchar()的循环,来判定是否是空格后的输入空格

后一种方法利用两个变量,首先对p赋值EOF作为初判定,后面的循环中p都为c上一个字符的值。

输入过程中,按下enter则开始进行本行的字符判定,涉及到键盘控制的输入过程,在C Primer Plus里面有讲,不提

#include <stdio.h>
int main()


{
	int c;
	while ((c=getchar())!=EOF)
	{
		if (c == ' ')
		{
			putchar(c);
			while ((c = getchar()) == ' ')
				;
		}
		else
		{
			putchar(c);
		}
	}
	return 0;
}

/*
{
	int c,pc;
	pc = EOF;
	while ((c=getchar())!=EOF)
	{
		if (c == ' ')
		{
			if (pc!=c)
			{
				putchar(c);
			}
		}
		else
			putchar(c);
		pc = c;
	}
	
	return 0;
}*/

说明一下,编的过程中曾出现while ((c = getchar() != EOF))这种低级错误,废了一会功夫。

!=的运算级别比=高,所以这种赋值就是c=( getchar() != EOF)得到判别式的值,通常为-1.但是如果输入EOF,也会结束循环,是因为输入EOF,c=0,表达式的值为0,while循环条件不满足。

手误眼误也就算了,关键是在调试过程中,无论输入任何字符,c的值都是1,这个时候竟然没意识过来是赋值问题。这个就很难以原谅了


P17编写函数power(num,i)来求幂

#include <stdio.h>
int power(int, int);
int main()


{
	int i=5;
	printf("%7d%7d%7d", i, power(-3, i - 1), power(2, i));
	getchar();
	return 0;
}

int power(int num, int n)
{
	if (n != 0)
	{
		if (n % 2 == 0)
		{
			return power(num*num, n / 2);
		}
		else
		{
			return power(num*num, n / 2)*num;
		}
	}
	else
		return 1;
	
}
这里面,重要的地方在于 power函数对自身的递归调用,这里每次调用n/2,可以实现递归次数的减半,符合最基本规则。而n%2==0的判断,则对于n是奇数的情况作了调整,多乘以一个num即可,不需要增加时间复杂度。这里对于每次n/2,将第一个实参改为num*num,不能写为power(power(num,n/2),2)    or    power(power(num,2), n/ 2),这是因为两个递归调用的函数中都有一个参数是常值2,永远不可能满足返回条件。

记住,递归调用的四个条件:

1 基准情形,必须有基准情形,无需递归就能解出。(这里面没写n==1的情况,相关分析在后面)

2 不断推进,每一次调用都要使求解情况朝基准情形推进。

3 设计法则,所有的递归调用都能运行,当设计递归程序时一般没有必要知道簿记管理的细节。

4 合成效益法则,在求解一个问题的同一实例时,切勿在不同的递归调用中做重复工作。

对于上面的递归程序,没有显式考虑到i=1的情况,但是在第一次调用时候如果i=1,自动归为

return power(num*num,n/2)*num;的情况,然后n/2==0,返回值为1,继而整个语句等价于return 1*num;  所以,没有错误。对于第一条的解读就是,要有返回语句,就是 满足什么条件时候可以返回一个什么样的值。


P21 通过比较得到输入文本行中的最长的文本行,并以EOF为结束条件,进行输出

#include <stdio.h>
int getline(char[], int lim);
void copy(char[], char[]);
#define MAXIMAM 100

int main()
{
	int length=0,i=0;
	char in[MAXIMAM], longest[MAXIMAM];

	while ((i=getline(in,MAXIMAM))>0)
	{
		if (i>length)//判断不能直接放在while循环里面
		{
			length = i;
			copy(longest, in);
		}		
	}

	if (length>0)
	{
		puts(longest);
	}

	getchar();
	return 0;
}
int getline(char in[], int lim)
{
	int i,length;
	char c;

	for (i = 0; i < lim - 1 && (c = getchar()) != EOF&&c != '\n';++i)
	{
		in[i] = c;
	}
	if (c == '\n')
	{
		in[i] = c;
		i++;
	}
	in[i] = '\0';//如果是EOF,则只对输入的字符串赋值给in[]为'\0',当时长度是0。在主函数中不满足循环条件,结束循环

	return i;
}
void copy(char out[], char in[])
{
	char c;
	int i = 0;
	for (; in[i] != '\0';i++)
	{
		out[i] = in[i];
	}
	out[i] = '\0';
}

两点,第一个是在

while((i=getline(in,MAXIMAM))>0)

if(i>length)

的判断中,不能直接把if的判断放入while中写作while((i=getline(in,MAXIMAM))>length),这样while的循环条件变为后面输入的文本行一定要比前面的长,不然结束循环。


第二个是,调用函数这边,算是自己的一个感悟。就是在while循环里面调用getline()函数,不是说直接把length&i之间长度的对比甚至while()循环也写在getline()里面,而是把修饰性步骤写在调用函数里面,被调用函数仅仅实现一个单纯的功能就是getline,不做复杂的修饰。这样在阅读程序时候看到while()循环和后面的长度对比就知道这整个组块是一个什么样的功能,而getline()函数就不用追究细节,知道输入输出接口就行,便于阅读代码。


练习1-20 编写程序detab,将输入中的制表符替换成适当数目的空格,使空格充满到下一个制表符终止位的地方。假设制表符终止位的位置是固定的,比如每隔n列就会出现一个制表符终止位。n应该作为变量还是符号常量?
本题的意思是TAB键是占8个空格位,如果输入ab/tdcd/t,则第一个/t由6个空格位替代,第二个/t由5个空格位替代。

#include <stdio.h>
#define TAB 8
int main(void)
{
	int i=0;
	char c;
	while ((c=getchar())!=EOF)
	{
		if (c != '\t')
		{
			putchar(c);
			i++;
			i = i % TAB;
		}
		else if (c=='\t')
		{
			while (TAB-(i++) > 0)
				putchar(' ');
		}
	}
	return 0;
}



题目1-21编写程序将输入中的空格键转换为最少的tab和空格键输出,其他字符不变


难点在于 连续的空格转换为\t输出时候,\t的输出格式与连续空格之前 总的字符数有关)如果存在9个空格,之前有13个字符(这里的13不应该包括这些连续的空格的数量)。一般认定9=8+1,输出一个\t和一个空格,但是实际上这里的\t只是3个空格的宽度,要补5个空格,其中5=numCHAR%8。如果存在第二个\t,也即如果这里空格数大于16,从第二个\t开始,输出空格数为8。

比较难.详细分析见注释,后面被注释掉的代码是别人的,可以作对比.写自己代码时候没有参考他的.

#include <stdio.h>
#define TAB 8
int main(void)
{
	char c;
	int numSPACE,num,numCHAR=0;

	while ((c=getchar())!=EOF)
	{
		numSPACE = 0;//每进行到这一步,说明之前的空格连续已经中断,numSPACE重置
			
		if (c != ' ')
		{
			numCHAR++;
			putchar(c);
			if (c=='\n')
			{
				numCHAR = 0;//遇到换行,则该行的字符总数置0
			}
		}			
		else
		{
			numSPACE = 1;//进入这个else就说明至少有一个空格,numSPACE加1
			while ((c = getchar()) != EOF&&c == ' ')
			{
				numSPACE++;
			}
			if (numSPACE>8)
			{
				for (num = numSPACE / 8; num > 0; num--)
					putchar('\t');//这里存在着一个陷阱,因为\t在输出时候是默认:输出空格个数(表示/t)和原来字符总数的和    为8的倍数
				num = numCHAR % 8;
				for (; num > 0; num--)
					putchar(' ');
			}			
			for (num = numSPACE % 8; num > 0; num--)
				putchar(' ');
			putchar(c);//最后一个空格之后的字符已经读入,在这里输出,不然会被遗弃
			numCHAR += numSPACE+1; //补充输出空格时候numSPACE不计这些连续的空格的数量,但是这些空格输出完后,这些“前面的字符总数”是计入这些连续空格数的,后面的+1是对上一行的putchar(c)计数
		}		
	}

	return 0;
}
/*
1-21 编写程序entab,将空格串替换为最少数量的制表符和空格#include <stdio.h>

#define TABINC 8           //tab increment size
思路: 先判断输入的字符类型
如果是空格 就++运算 统计空格的数量
如果是换行,就把所有的统计数据清空
否则就进行替换运算,先根据制表符的公式求出第一个制表符需要的空格数,
如果空格总是大于制表符的位数,就循环减去制表符的位数,
如果空格小于制表符的位数,就循环输出空格

//replace strings of blanks with tabs and blanks
int main(void)
{
	int c;
	int nbspLength = 0;             空格的长度 	int charLength = 0;             字符串的长度 	int tabLength = 0;             如果出现字符少于8位制表符的位数 

	while ((c = getchar()) != EOF){
		//是回车  清空统计数据
		if (c == '\n'){
			nbspLength = 0;
			charLength = 0;
		}
		//是空格 统计空格数量
		if (c == ' '){
			nbspLength++;
		}
		//如果存在空格
		else if (nbspLength>0){
			tabLength = (TABINC - charLength) % TABINC;
			//可以用tab代替
			if (tabLength>0 && nbspLength >= tabLength){
				putchar('\t');
				nbspLength -= tabLength; //因为用tab代替了,所以减去tab的长度数量的空格数量
			}
			//代替了之后存在的空格数量大于TABINC,可以继续用tab代替
			if (nbspLength>TABINC){
				while (nbspLength>TABINC)
				{
					putchar('\t');
					nbspLength -= TABINC;
				}
				charLength = nbspLength;//关键!
				while (nbspLength>0){
					putchar(' ');
					nbspLength--;
				}

			}
			else{
				charLength = nbspLength;
				while (nbspLength>0)
				{
					putchar(' ');
					nbspLength--;
				}
			}

		}
		putchar(c);
		charLength++;
		nbspLength = 0;
	}

	return 0;
}*/

看着没问题?但是上面的38行判断条件if(numSPACE)>8是错的,一般认为只有numSPACE大于8时候才有可能输出\t,但是在 前面已经有13个字符时,输出的/t将只占3个空格。那么按照题目的意思,所有的空格能用\t代替就要用,这里错。

判断条件应该改为

if(numSPACE>8-numCHAR%8)这时候的执行语句应该首先putchar('\t');

然后,判断numSPACE-8+numCHAR%8就是输出这个\t后还应输出的空格数,如果大于8,再用for循环输出剩下的(numSPACE-8+numCHAR%8)/8个\t。用for循环输出(numSPACE-8+numCHAR%8)%8个空格。

38行code后面的执行语句可以正确输出应有的空格间距,但是判断语句没有最大程度上利用\t。



1-22编写一个程序,将较长的输入行“折”成短一些的两行或多行,折行的位置在输入行的第n列之前的最后一个非空格之后。要保证程序能够智能地处理输入行很长以及在指定的列前没有空格或制表符时的情况。

#include <stdio.h>
#define MAX 15

int main(void)
{
	char inPUT[MAX+MAX/3],inPUT2[MAX],c,tmpCHAR;
	int i=0,j,state,stateI;//stateI专门测试I的状态
	
	while ((c = getchar()) != EOF)
	{
		state = 0;
		stateI = 0;
		inPUT[i++] = c;
		for (; i < MAX && (c = getchar()) != EOF; i++)
		{
			if (c == '\n' || c=='\0')
			{
				inPUT[i] = '\0';
				puts(inPUT);
				stateI = 1;
				break;
			}
			else
				inPUT[i] = c;

		}

		/*if ((c = getchar()) == EOF)//这里也不能这么处理,因为前MAX个字符里面可能存在空格或者'\t',要换行输出
		{
			inPUT[i + 1] = '\0';
			puts(inPUT);
			break;
		}*/
		
		if (stateI==1)//就是输入的前MAX个字符里面有\n
		{
			i = 0;
			continue;//将会重新开始循环
		}

		for (i = MAX-1; i>=0;i--)//往回找最近的空格或\t
		{
			if (inPUT[i] == ' ' || inPUT[i] == '\t')
			{
				tmpCHAR = inPUT[i + 1];
				inPUT[++i] = '\0';
				if (i==MAX)
				{
					i = 0;
					stateI = 1;
				}
				puts(inPUT);
				state = 1;
				break;
			}
		}
		if (stateI)//就是说恰好在第MAX个字符符合换行条件
		{
			continue;
		}
		if (state)//如果state==1,说明前MAX个字符里面有空格或者\t,需要对已经读入的   后面的字符重新写入inPUT中。这里隐含条件是空格位置不在MAX处
		{
			inPUT[0] = tmpCHAR;
			for (i++, j = 1; i < MAX; i++, j++)
				inPUT[j] = inPUT[i];
			i = j;//输出之后,下次再用c=getchar()输入从i=j开始往inPUT数组里面输入
		} 
		else
		//说明指定列前没有空格或制表符,这里给出MAX/3的最大额外限度。如果MAX/3范围内有相关字符,则输出相应范围内的字符;如果没有,则直接输出前MAX个字符
		{
			for (i = MAX; i < MAX + MAX / 3 - 1 && (c = getchar()) != EOF; i++)
			{
				if (c != ' ' && c != '\t'&&c!='\n')
					inPUT[i] = c;
				else
				{
					inPUT[i] = c;
					i++;
					inPUT[i] = '\0';
					puts(inPUT);
					i = 0;
					state = 1;
					break;
				}
			}

			if (!state)//就说明即使扩展MAX/3,还是没有空格键
			{
				for (i = MAX, j = 0; i < MAX + MAX / 3 - 1 ; i++,j++)
				{
					inPUT2[j] = inPUT[i];
				}
				inPUT[MAX] = '\0';
				puts(inPUT);
				i = j;
				for (; j >= 0; j--)
					inPUT[j] = inPUT2[j];
			}
		}
	}
}

程序的设计思想是

输入字符后读入到一个长度为MAX的数组中,如果出现'\0'  '\n'则直接输出;如果读到MAX,则倒向遍历,遇到\n  \t   \0则直接输出(因为题目要求的是固定长度MAX前的第一个“空格类”字符。这里存在一个难点就是如果输出前面的字符,后面的已经遍历的需要重新赋值,便于下次输出。这个好处理。遇到的第一个“空格类”字符后面的字符存到一个临时变量,然后 相应位置赋值为'\0',可以直接用puts()输出。至于已经读入数组的没有输出的字符,先存入tmpCHAR的字符,再用inPUT[j] = inPUT[i];读入,这里需要注意的是,要用i=j;来存入数组的最后的有效下标大小)

如果前面MAX大小的字符没有“空格类”字符,则向后扩展MAX/3的空间,继续之前的检查,如果出现了“空格类”,就直接输出(这里不是倒向遍历,而是对每一个输入的字符都进行检查,符合条件就按照前面的步骤进行输出和后续赋值)

如果向后扩展MAX/3空间依然无效,就输出MAX大小的字符,后面的MAX/3的字符重新读入数组,便于下次输出(输出和重置方式如上)

整个程序难点在于 数组下标i的设置、state的状态设置、对字符的读入方式和检查方式。其中,如果输入一个字符串,按下回车键。回车键在这里相当于 命令读入键盘输入的字符。

有一种情况是如果连续输入24个字符,按下回车键,程序输出15个,剩9个。但是程序不会对这9个字符进行处理,而是等待输入满足15个字符之后才进行处理。这时候再按回车键就相当于'\n'。



下面分析一下答案的做法:

#include <stdio.h>

#define MAXLINE 1000 /* max input line size */

char line[MAXLINE]; /*current input line*/

int getline(void); /* taken from the KnR book. */


int
main()
{
	int t, len;
	int location, spaceholder;
	const int FOLDLENGTH = 70; /* The max length of a line */

	while ((len = getline()) > 0)
	{
		if (len < FOLDLENGTH)
		{
		}
		else
		{
			/* if this is an extra long line then we
			** loop through it replacing a space nearest
			** to the foldarea with a newline.
			*/
			t = 0;
			location = 0;
			while (t<len)
			{
				if (line[t] == ' ')
					spaceholder = t;

				if (location == FOLDLENGTH)
				{
					line[spaceholder] = '\n';
					location = 0;
				}
				location++;
				t++;
			}
		}
		printf("%s", line);
	}
	return 0;
}
/* getline: specialized version */
int getline(void)
{
	int c, i;
	extern char line[];

	for (i = 0; i<MAXLINE - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
		line[i] = c;
	if (c == '\n')
	{
		line[i] = c;
		++i;
	}
	line[i] = '\0';
	return i;

}

调试运行,我不知道这个答案要实现什么功能。这是哪门子答案?答案来自 http://read.pudn.com/downloads112/ebook/466522/C%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E8%AF%AD%E8%A8%80%EF%BC%88%E7%AC%AC%E4%BA%8C%E7%89%88%EF%BC%89%E8%AF%BE%E5%90%8E%E7%AD%94%E6%A1%88.pdf




1-23编写一个删除C语言程序中所有的注释语句。要正确处理带引号的字符串与字符常量。在C语言中,注释不允许嵌套。

这个参考http://blog.csdn.net/yuezhiren/article/details/7957823答案中的From Chris Mears,从两份参考的情况来看,这道题目:删除C语言程序中所有的注释语句(正确处理带引号的字符串与字符常量),更多的是要考虑到各种 ' " \\ / * \n 子这些字符的排列组合以及这些排列组合所应对的处理方法,主要用到了状态机

决定参考http://blog.csdn.net/yuezhiren/article/details/7957823,这个方法好在

1 引入状态机,而答案中的From Chris Mears更多地是用一些很复杂的前状态来遍历所有可能的情况,代码复杂  

2 和答案中的From Chris Mears方法不同,http://blog.csdn.net/yuezhiren/article/details/7957823是先把各种可能的状态全部列出,最后对所有的状态分类:哪些状态输出字符,哪些状态按照错误处理,哪些状态不输出。

测试结果是http://blog.csdn.net/yuezhiren/article/details/7957823的代码在处理//时候不正确,答案结果正确。

/*state -1:错误状态
跳出循环,输出错误信息;
state 0:启示状态
输入"	进入状态1
输入‘	进入状态3
输入/	进入状态8
输入其他字符,继续留在状态0
state 1:	"
输入"	进入状态2
输入其他字符,在字符串里面,继续留在状态1
state 2:	"..."
结束字符串,进入状态0
state 3:	'
输入\	进入状态14
输入其他字符,进入状态6
state 14:	'\
输入任意字符进入状态4
state 4:	'\_
输入'	进入状态5
输入其他字符,说明出错,进入状态-1
state 5:	'\_'
结束这个转义字符,进入状态0
state 6:	'_
输入'	进入状态7
输入其他字符,进入状态-1
state 7:	'_'
结束字符,进入状态0
state 8:	/
输入*	进入状态9
输入/	进入状态12
输入其他字符进入状态15
state 9:	/*  块注释
输入*	进入状态10
输入其他字符留在状态9
state 10:	/*...*
输入/	进入状态11
输入其他字符进入状态9
state 11:	结束块注释
进入状态0
state 12:	//  行注释
输入\n  进入状态13
输入其他字符留在状态12
state 13:	结束行注释
进入状态0
state 14:	'\
输入任何字符进入状态5,相当于这里是一个转义字符
state 15:	/
没有考虑到除法的运用,应该对c进行判断。如果是数字(问题是,如果#define NUM 8  然后average=sum/NUM 怎么办?),所以,状态机的方法有本质缺陷
对上面状态的分类:
输出字符:0 1 2 3 4 5 6 7 14 15
不输出字符:8 9 10 11 12 13*/



#include <stdio.h>
#include <string.h>
#define STATE 16
static char fsm[STATE][128];
void init_fsm()
{
	const int len_of_line = sizeof(fsm) / STATE;

	memset(fsm[0], 0, len_of_line);
	fsm[0][int('"')] = 1;
	fsm[0][int('\'')] = 3;//这里注意几种字符的写法
	fsm[0][int('/')] = 8;

	memset(fsm[1], 1, len_of_line);
	fsm[1][int('"')] = 2;

	memset(fsm[2], 0, len_of_line);
	memset(fsm[3], 6, len_of_line);
	fsm[3]['\\'] = 14;

	memset(fsm[4], -1, len_of_line);
	fsm[4][int('\'')] = 5;

	memset(fsm[5], 0, len_of_line);
	memset(fsm[6], -1, len_of_line);
	fsm[6][int('\'')] = 7;

	memset(fsm[7], 0, len_of_line);
	memset(fsm[8], 15, len_of_line);
	fsm[8][int('*')] = 9;
	fsm[8][int('/')] = 12;

	memset(fsm[9], 9, len_of_line);
	fsm[9][int('*')] = 10;

	memset(fsm[10], 9, len_of_line);
	fsm[10][int('/')] = 11;
	memset(fsm[11], 0, len_of_line);
	memset(fsm[12], 12, len_of_line);
	fsm[12][int('\n')] = 13;
	memset(fsm[13], 0, len_of_line);
	memset(fsm[14], 4, len_of_line);
	memset(fsm[15], -1, len_of_line);//下面几个应用到的是/除法后面跟的要么是变量名,要么是数字。
	for (int i = '0'; i <= '9';i++)
	{
		fsm[15][i] = 0;
	}
	for (int i = 'a'; i <= 'z'; i++)
	{
		fsm[15][i] = 0;
	}
	for (int i = 'A'; i <= 'Z'; i++)
	{
		fsm[15][i] = 0;
	}
	fsm[15]['_'] = 0;
}

int main(void)
{
	char state = 0;
	char c;
	FILE * fin, *fout;
	fopen_s(&fin,"in1.c", "r");
	fopen_s(&fout,"out.c","w");

	init_fsm();//这里相当于声明了fsm这个数组

	while ((c=fgetc(fin)) != EOF)
	{ 
		if (int(c)>0)//在状态机的设定中,没有c<0的情况。但是如果读入字符为汉字,c<0,这种情况下一般是注释。为了保证原状态,如果c<0,state值不变
		{			//但是答案中的方法不会出现这种问题。状态机的所有状态基本需要有限可列,属于缺点
			state = fsm[state][int(c)];
		}
		
		if ( state== -1)
			break;
		switch (state)
		{
		case 0:
		case 1:
		case 2:
		case 3:
		case 4:
		case 5:
		case 6:
		case 14:
		case 15:
		case 7:	fputc(c,fout);
			break;
		default:
			break;
		}
		if (c=='\n')
		{
			fputc(c, fout);
			state = 0;
		}
	}

	fclose(fin);
	fclose(fout);

	return 0;
}

1. 刚开始用fscanf&fprintf,不对,因为这里的c是逐字符读取。
2. 之前没有写
if (c=='\n')
state=0;
其实在 换行符 后,默认初始化才是正确的;但是在没有这句话时候,state!=0;   这时候重新读入字符c,状态判断和状态重新赋值是错的。
因为之前的state就是错的,则后面的都会错。

3. 就是汉字的问题和除法中用到/的问题,这说明状态机这个方法有先天性缺陷。

但是状态机这个方法贵在简单易懂,在分析完所有的情况之后可以清晰地把所有的情况用代码表示出来。


下面将会分析答案中的From Chris Mears方法。

<span style="font-size:14px;">#include <stdio.h>
enum state_t { normal, string, character, block_comment, line_comment };
enum token_t { none, backslash, slash, star, tri1, tri2, tri_backslash };
static int print_mode(enum state_t s)
{
	return (s == normal || s == string || s == character);
}
void cstrip(FILE *infile, FILE *outfile)
{
	char ch;
	int comment_newline = 0;
	enum state_t state = normal;
	enum token_t token = none;/*token只有7种状态,所以普通字符应该不进行更改*/
	enum token_t last_token = none;
	if (!infile || !outfile || (infile == outfile)) {
		return;
	}
	while ((ch = fgetc(infile)) != EOF) {
		switch (ch) {
		case '/':
			if (token == tri2) 
			{
				token = tri_backslash;
				if (print_mode(state))
					fputc(ch, outfile);
			}
			else if (state == string || state == character) 
			{
				fputc(ch, outfile);
				token = slash;
			}
			else if (state == block_comment && token == star) 
			{
				state = normal;
				token = none;
				/*Replace block comments with whitespace.*/
				if (comment_newline) 
				{
					fputc('\n', outfile);
				}
				else 
				{
					fputc(' ', outfile);
				}
			}
			else if (state == normal && token == slash)
			{
				state = line_comment;
				token = slash;
			}
			else 
			{
				token = slash;
			}
			break;
		case '\\':/*就是字符\,转义字符表达为'\\'*/
			if (state == normal && token == slash)/*普通状态下遇到/\   */
				fputc('/', outfile);
			if (print_mode(state))/*一般只有/出现不输出的状况,其他的字符都是需要state&token协同判断*/
				fputc(ch, outfile);
			if (token == backslash || token == tri_backslash) 
			{
				token = none;
			}
			else 
			{
				last_token = token;/*last_token表示token之前的token*/
				token = backslash;
			}
			break;
		case '"':
			if (state == normal && token == slash)/*普通状态下遇到/"   */
				fputc('/', outfile);
			if (state == string && token != backslash)
				state = normal;
			else if (state == normal && token != backslash)
				state = string;
			if (print_mode(state))/*这里的state判断在上面的state==normal和state==string之后,所以对 " 的输出处理操作是可以用后续if处理*/
				fputc(ch, outfile);
			token = none;//更新token
			break;
		case '\'':
			if (state == normal && token == slash)/*普通状态下遇到/' */
				fputc('/', outfile);
			if (state == character && token != backslash)
				state = normal;
			else if (state == normal && token != backslash)
				state = character;
			if (print_mode(state))
				fputc(ch, outfile);
			token = none;
			break;
		case '\n':
			/*This test is independent of the others.*/
			if (state == block_comment)
				comment_newline = 1;
			if (state == normal && token == slash)
				fputc('/', outfile);
			if (token == backslash || token == tri_backslash)
				token = last_token;
			else if (state == line_comment &&token != backslash) //判定为行注释结束
			{
				state = normal;//如果是行注释,这里变为normal后,后面的state判断输出,恰好用到,输出换行
				token = none;
			}
			else {
				token = none;
			}
			if (print_mode(state))
				fputc(ch, outfile);
			break;
		case '*':
			if (state == normal && token == slash) 
			{
				state = block_comment;
				token = none;
				comment_newline = 0;//进入块注释,块注释中的换行标志置0
			}
			else 
			{
				token = star;
			}
			if (print_mode(state))
				fputc(ch, outfile);
			break;
		case '?'://这里的?就是字符?,不是读入汉字时的?
			if (state == normal && token == slash)
				fputc('/', outfile);
			if (token == tri1) {
				token = tri2;
			}
			else if (token == tri2) {
				token = tri2; /*retain state。tri_代表三字母*/
			}
			else {
				/*We might need the last token if this
				* trigraph turns out to be a backslash.
				*/
				last_token = token;
				token = tri1;
			}
			if (print_mode(state))
				fputc(ch, outfile);
			break;
		default:
			if (state == normal && token == slash)
				fputc('/', outfile);
			if (print_mode(state))
				fputc(ch, outfile);
			token = none;
			break;
		} /*switch */
	} /*while*/
	return;
}
/*Small driver program.*/
int main(void)
{
	FILE *f_in, *f_out;
	fopen_s(&f_in, "in1.c", "r");
	fopen_s(&f_out, "out_pdf.c", "w");
	cstrip(f_in,f_out);
	return 0;
}</span>

看不太懂,慢慢悟吧


1-24 编写程序,查找C语言程序中的基本语法错误,如圆括号、方括号、花括号不配对等。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MaxSize 20
#define EmptyTOY -1

struct StackRecord;
typedef struct StackRecord *Stack;
void synDetect(FILE *);
void Push(Stack, char);
char Pop(Stack);

struct StackRecord
{
	int Capacity;
	char *Array;
	int TopOfStack;
}*synDetector;


#include "标头.h";

int main(void)
{
	FILE *fin;
	fopen_s(&fin, "in.c", "r");
	if (!fin)
	{
		printf("Error in opening files.\n");
		exit(1);
	}

	synDetect(fin);
	getchar();
	return 0;
}
void synDetect(FILE *fin)
{
	char c,tmp;
	synDetector = (struct StackRecord *)malloc(sizeof(struct StackRecord));
	synDetector->Capacity = MaxSize;
	synDetector->Array = (char *)malloc(synDetector->Capacity*sizeof(char));
	synDetector->TopOfStack = EmptyTOY;

	while ((c = fgetc(fin)) != EOF)
	{
		switch (c)
		{
		case '{':
			Push(synDetector, c);
			break;
		case '[':
			Push(synDetector, c);
			break;
		case '(':
			Push(synDetector, c);
			break;
		case ')':
			tmp = Pop(synDetector);
			if (tmp != '(')
			{
				printf("Something wrong have happened.\nIt needs to be symmetric with %c, but the stack pops %c.\n",c,tmp);
				getchar();
				exit(1);
			}
			break;
		case ']':
			tmp = Pop(synDetector);
			if (tmp != '[')
			{
				printf("Something wrong have happened.\nIt needs to be symmetric with %c, but the stack pops %c.\n", c, tmp);
				getchar();
				exit(1);
			}
			break;
		case '}':
			tmp = Pop(synDetector);
			if (tmp != '{')
			{
				printf("Something wrong have happened.\nIt needs to be symmetric with %c, but the stack pops %c.\n", c, tmp);
				getchar();
				exit(1);

			}
			break;
		default:
			break;
		}
	}
	if (c==EOF)
	{
		printf("Nothing has been detected to be wrong.\n");
	}
	else
		printf("Something wrong have happened in proceeding to the end of the file.\n");

}
void Push(Stack synDetector, char c)
{
	if (synDetector->TopOfStack==MaxSize)
	{
		printf("The capacity of STACK has been used up.\n");
		exit (1);
	}
	synDetector->TopOfStack++;
	synDetector->Array[synDetector->TopOfStack] = c;

}
char Pop(Stack)
{
	if (synDetector->TopOfStack == EmptyTOY)
	{
		printf("There is nothing in the STACK .\n");
		exit(1);
	}
	char tmp = synDetector->Array[synDetector->TopOfStack];
	synDetector->TopOfStack--;
	return tmp;
}

答案上的方法的改写版,功能更加强大:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

struct MyStruct
{
	char *Array;
	int TopOfArray;
	int Capacity;
};
typedef struct MyStruct *Stack;
#define MaxSize 20
#define EmptyTOY -1
void SymmetryDetect(FILE *);
enum state_t  { normal, comment, siglequote, doublequote };



/*这是仿效答案的方法写的。这种方法有一个好处是state变量可以“正确处理引号(包括单引号和双引号)、转义字符序列与注释”。
用到了栈。另外需要加入一个功能:在碰到所有的{时候都需要验证在栈里没有剩余的(或者[,因为{}成对出现标志着一个大的语句的
开始或者结束。这中间不可能有(或者[ */
#include "标头.h"

int main(void)
{
	FILE *fin;
	fopen_s(&fin, "in.c", "r");

	SymmetryDetect(fin);
	getchar();
	return 0;
}
void SymmetryDetect(FILE* fin)
{
	if (!fin)
	{
		printf("Error in file in.c.\n");
		getchar();
		exit (1);
	}

	char c, tmp;
	bool flag = true;//验证是否在栈中新读入的 { 下面存在(或者[
	int line = 1,i;/*用于在提示语句中输出出现问题的位置*/
	enum state_t state = normal;
	Stack SymDetector;
	SymDetector = (Stack)malloc(sizeof(struct MyStruct));
	SymDetector->Array = (char *)malloc(sizeof(char)*MaxSize);
	SymDetector->Capacity = MaxSize;
	SymDetector->TopOfArray = 0;

	while ((c = fgetc(fin)) != EOF)
	{
		if (c=='\n')
		{
			line++;
			continue;
		}
		switch (state)
		{
		case normal:
			if (c=='\'')
			{
				state = siglequote;
			}
			else if (c=='\"')
			{
				state = doublequote;
			}
			else if (c=='/')
			{
				tmp = fgetc(fin);
				if (tmp == '*')
				{
					state = comment;//only care about /*...*/, means block comment.
				}
				else
					ungetc(tmp,fin);
			} 
			else if (c=='('||c=='['||c=='{')
			{
				if (!(SymDetector->TopOfArray<MaxSize))
				{
					printf("The size has been used up.\n");
					getchar();
					exit(1);
				}
				if (c == '{')
				{
					i =SymDetector->TopOfArray-1;
					for (; i >= 0;i--)
					{
						if (SymDetector->Array[i] == '(' || SymDetector->Array[i] == '[')
						{
							printf("The char which is detected now is {, but there is %c left in the array before line %d.\n", SymDetector->Array[i], line);
							flag = false;
						}
					}
					if (flag==false)
					{
						getchar();
						exit(1);
					}
				}
				SymDetector->Array[SymDetector->TopOfArray++] = c;
			}
			else if (c == ')' || c == ']' || c == '}')
			{
				if (!(SymDetector->TopOfArray > EmptyTOY))
				{
					printf("There's nothing left to be counterpart with %c in the line of %d.\n",c,line);
					getchar();
					exit(1);
				}
				tmp = SymDetector->Array[--SymDetector->TopOfArray];
				if (!((tmp =='('&&c == ')') || (tmp == '['&&c == ']')||(tmp == '{'&&c == '}')))
				{
					printf("There is no char can be counterpart with %c in the line of %d, rather, the tmp=%c.\n", c, line,tmp);
					getchar();
					exit(1);
				}
			}
			break;
		case comment:
			if (c='*')
			{
				tmp = fgetc(fin);
				if (tmp == '\\')
				{
					state = normal;
				}
				else
					ungetc(tmp, fin);
			}
			break;
		case siglequote:
			if (c=='\'')
			{
				state = normal;
			}
			else if (c == '\\')
			{
				fgetc(fin);//把单引号里面的\'中的'排除。因为在程序中,写'这个字符是按照\'的格式,但是在fgetc()中,这是两个字符
			}
			break;
		case doublequote:
			if (c=='"')
			{
				state = normal;
			} 
			else if (c == '\\')
			{
				fgetc(fin);
			}
			break;
		}
	}
	
	if ((SymDetector->TopOfArray >0))
	{
		int i = -- SymDetector->TopOfArray;
		printf("These chars have no counter part in the code:");
		for (; i >= 0; i--)
			printf(" %c", SymDetector->Array[i]);
		printf(".\n");
	}
	else if (c == EOF)
	{
		printf("Noting wrong in the using of (, [ and { has happened.\nAnd this is the end of the code.\n\n");
	}
	else
	{
		printf("Something wrong.\n");
	}

	switch (state)
	{
	case normal:
		printf("Code ends in a proper way, which means state=normal.\n");
		break;
	case comment:
		printf("Code ends inside the comment.\n");
		getchar();
		exit(1);
	case siglequote:
		printf("Code ends inside the Single quote.\n");
		getchar();
		exit(1);
	case doublequote:
		printf("Code ends inside the double quote.\n");
		getchar();
		exit(1);
	}
	
	return;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值