K&R exercise 4-5,4-6
在day13的基础上
1. 增加访问sin、exp与pow等库函数的操作。
思路:
刚开始的解决方案:
对每个math function确立相对应的标志,看起来简单直接。
case'e':
push(exp(pop()));
break;
出现的问题:
若每一个math function都有对应的标志,这样容易增加主函数的工作量,因为库函数的数量很多。所以对于所有的库函数来说,是否可以只设置一个标志呢?
改进措施:
在设置一个标志的情况下,如果可以将这些数学函数通过调用一个函数去解决。那就提高程序运行的效率。
可以借鉴之前通过getop函数获取操作数的方法。若获得的是不同的数字字符串而只返回一个标志,表明此时读取到的是一个数字,在调用atof函数将字符串转化为浮点数。所以,我们也可以定义一个标志表明所读取的是一个字符串,若字符串为数学函数名就执行相应的操作。
2. 增加了处理变量的命令,还增加了一个变量存放最近打印的值。
思路:
提供输入26个具有单个英文字母变量名的变量很容易,因为将这些字母作为数组变量的索引可以十分方便存放赋给每个字母变量的值。
同时还需要增加新的操作符,起作用就是将堆栈中的某个元素赋值给’='前的字母变量。例如:
3 A =
就是将3赋给变量A。
由此对主函数所增加(改进)的程序段如下所示:
case'='://RPN notation for assigning to variables
pop();//先弹出变量原先的值
if (var >= 'A' && var <= 'Z')
variable[var - 'A'] = pop();//将需要更新的值弹出,并赋值到输入变量对应的索引处
else
printf("error: no variable\n");
break;
default:
if (type >= 'A' && type <= 'Z')//A-Z作为数组变量的索引,同时也代表输入的变量
push(variable[type - 'A']);//将变量对应位置的值压入到栈中,所压入的值是该变量执行赋值操作前的值
else if (type == 'v')
push(v);
else
printf("error:unknown command %s\n", s);
break;
}
var = type;
测试结果:
反思:
通过分析会发现一些令人疑惑的地方:
如图1所示,为什么一读取到输入的变量就要将其目前值压入堆栈中,这样不会使得之后再对该变量进行赋值操作时,如图2所示,还需要多执行一次pop操作,才能更新相应索引位置的值。
所以我们如果把图一程序段和图二中的第一次执行pop操作语句去掉,会出现什么问题。
if (type >= 'A' && type <= 'Z')//A-Z作为数组变量的索引,同时也代表输入的变量
push(variable[type - 'A']);//将变量对应位置的值压入到栈中,所压入的值是该变量执行赋值操作前的值
图1
case'='://RPN notation for assigning to variables
pop();//先弹出变量原先的值
if (var >= 'A' && var <= 'Z')
variable[var - 'A'] = pop();//将需要更新的值弹出,并赋值到输入变量对应的索引处
图2
去掉之后,测试结果如下:
当只进行对变量的赋值操作时,两者其实并不太大区别。但当进行变量之间的加减乘除运算时,程序会显示无法执行弹出操作,因为栈此时为空。所以每当读取到一个变量,就需要将其当前值压入到堆栈中,这是为了后边的计算做一个铺垫。因为所有运算都需要从堆栈中弹出数字后才能开始。
完整程序如下:
//逆波兰计算器 reverse Polish calculator,simple "RPN"
#define number '0' //在输入中找到一个数的标志
#define Name 'n' //在输入中找到"名字"的标志
#define maxop 100 //操作数或运算符的最大长度
char push(double);
double pop(void);
int getop(char []);
double mathfunc(char[]);
void ClearStack(void);
int main()
{
int type, i, var=0;
double op, op2, v;//操作数operand
char s[maxop];
double variable[26];
for (i = 0; i < 26; i++)
variable[i] = 0.0;
while ((type=getop(s))!=EOF)
{
switch (type)
{
case number://char类型的数据可以自动转换为int类型的数据
//当getop函数想要返回一个数值操作数时,并不能像返回运算符一样
//直接将字符串s中的内容赋值给type,所以需要一个标志来表示返回数值操作数的情况
//本程序选择'0'为这种情况的标志,随后将字符串s转化为浮点数
push(atof(s));
break;
case '+':
push(pop() + pop());
break;
case '-':
op2 = pop();
push(pop() - op2);
break;
case '*':
push(pop() * pop());
break;
case '/':
op2 = pop();
if (op2 != 0.0)
push(pop() / op2);
else
printf("error:zero divisor\n");
break;
case'%':
op2 = pop();
if (op2 != 0)
push(fmod(pop(), op2));
else
printf("error: zero disvisor\n");
break;
case'c'://clear the stack
ClearStack();
break;
case'p'://print top element
op2 = pop();
printf("\t%.8g\n", op2);
push(op2);
break;
case'd'://duplicate top element of the stack
op2 = pop();
push(op2);
push(op2);
break;
case Name:
mathfunc(s);
break;
case's'://swap the top two characters
op = pop();
op2 = pop();
push(op);
push(op2);
break;
case '\n'://每次输入运算符和操作数后都会以换行符做结尾,所以'\n'的出现
//表示一次运算的结束,此时就可以打印计算结果
v = pop();
printf("\t%.8g\n", v);
break;
case'='://RPN notation for assigning to variables
pop();//先弹出变量原先的值
if (var >= 'A' && var <= 'Z')
variable[var - 'A'] = pop();//将需要更新的值弹出,并赋值到输入变量对应的索引处
else
printf("error: no variable\n");
break;
default:
if (type >= 'A' && type <= 'Z')//A-Z作为数组变量的索引,同时也代表输入的变量
push(variable[type - 'A']);//将变量对应位置的值压入到栈中,所压入的值是该变量执行赋值操作前的值
else if (type == 'v')
push(v);
else
printf("error:unknown command %s\n", s);
break;
}
var = type;
}
return 0;
}
//mathfunc:check if string s is one of supported math functions for "math.h"
double mathfunc(char s[])
{
double op2;
if (strcmp(s, "sin") == 0)
push(sin(pop()));
else if (strcmp(s, "cos") == 0)
push(cos(pop()));
else if (strcmp(s, "exp") == 0)
push(exp(pop()));
else if (strcmp(s, "pow") == 0)
{
op2 = pop();
pow(pop(), op2);
}
else
printf("%s is wrong math function!!! Not supported!!!",s);
}
#define maxval 100
int strplace = 0;//栈中下一个空闲的位置(栈顶指针)
double val[maxval];//栈用顺序表来存放
char push(double f)
{
if (strplace < maxval)
{
val[strplace++] = f;
}
else
printf("error:stack has been full,can't push\n");
}
double pop(void)
{
if (strplace > 0)
{
return val[--strplace];//example:1 2 +
//'+'不需要进行pop操作,所以先进行自减操作,然后再返回
}
else
{
printf("error:stack empty,can't pop\n");
return 0.0;
}
}
void ClearStack(void)
{
strplace = 0;
}
int getch();
void ungetch(int);
//getop函数用来获取下一个运算符或数值操作数
int getop(char s[])
{
int c , i;
//(s[0] = c = getch()) == ' ' || c = '\t'系统提示表达式必须是可修改的左值
//原因是此时||运算符右侧为赋值语句,那么其右侧逻辑值必定为1,导致循环条件逻辑值必为1,无法修改
while ((s[0] = c = getch()) == ' ' || c == '\t')
;//该while循环语句的内部执行顺序:
//首先从输入内容中读取一个字符,即c=getch()
//接着将字符c放入字符串中,s[0]=c
//所以说循环语句的条件是c == ' '||c == '\t',为了跳过空格和换行符
s[1] = '\0';//为什么要在这里设置终止符'\0'以及终止符的作用
//如果读取的字符为操作符,那么就需要将字符串s的长度设置为1,那么就需要将终止符'\0'放在s[1]处
//终止符作用:标记字符串的结束位置,从而使得strlen()等其他对字符串进行读写操作的函数知道什么时候
//应该停止读取字符串s
i = 0;
if (!isdigit(c) && c != '.' && c != '-' && !islower(c))//遵循从左到右的顺序进行判断
return c;
if (c == '-')
{
if (isdigit(c = getch()) || c == '.')
s[++i] = c;
else
{//程序要确定读取数字的完整性,所以需要多读一个字符
if (c != EOF)
ungetch(c);
return '-';//返回输入的结尾
}
}
if (islower(c))//如果c为小写字母
{
while (islower(s[++i] = c = getch()))
;
s[i] = '\0';
if (c != EOF)//不管所读取的是单个小写字母,还是一个小写字母组成的字符串
//为了确定所读取内容的准确性,都需要多读取一个字符,然后将其“压回”栈中
ungetch(c);
if (strlen(s) > 1)
return Name;
else
return c;
}
//收集数据,为了收集完整的数,需要多进行一次判断,而getch函数中调用的getchar函数不具备
//记录每次读取内容的能力。所以在读取到非数字内容时,应该将其压入到缓存区当中(也就是执行ungetch函数)
if (isdigit(c))//收集整数部分
while (isdigit(s[++i] = c = getch()));
if(c=='.')//收集小数部分
while (isdigit(s[++i] = c = getch()));
s[i] = '\0';
if (c!= EOF)
ungetch(c);
//getch()与ungetch()的用途:
//经常出现的一种情况是程序不能确定它锁读入的输入是否足够,除非超前读取一些输入
//在该程序中,读取一些字符以合成数字就会出现这种情况。因为它无法确定已经读取的数字是否完整。
//所以就需要多读取一些输入。但这又会出现的问题是多读取的内容不属于当前所要读入的数字
//解决方法:
//如果能够“反读”不需要的字符,也就是将多读取(已读取)的内容在压回到输入中。这就相当于没有读入该内容一样
//可以通过getch()与ungetch()这对互相协作的函数来进行该操作。getch函数用于读取下一个待处理的字符,
//ungetch函数用于把(多读取的)字符放回到输入中,更具体一些,就是将字符先放在缓存区中。当它不为空时,
//getch函数先从缓存区中读取字符,之后再调用getchar函数读取输入。
return number;
}
#define bufsize 100
//缓存区相关外部变量:
//为了判断缓存区是否为空,这里还需要一个下标变量来记录缓存区中当前字符的位置
//由于缓存区与下标变量是getch()与ungetch()两个函数需要共享的信息,同时在调用的过程中值不能发生变化
//对于其他函数也需要隐藏,所以要将它们设置为这两个函数的外部变量
char buf[bufsize];
int bufp = 0;//缓存区buf中下一个空闲位置
//当缓存区buf为空时,执行getchar函数,它从输入中读取一个字符,相当于清除掉输入中的该字符
int getch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
//--bufp的原因:
//因为bufp表示缓存区中下一个空闲位置,那说明该位置此时无字符,所以需要自减后再进行读取操作
}
void ungetch(int ch)
{
if (bufp >= bufsize)
printf("too many characters in buf\n");
else
buf[bufp++] = ch;
}
更新(2022.2.14 18:16):
上面的程序有错误的地方
错误程序段:
if (islower(c))//如果c为小写字母
{
while (islower(s[++i] = c = getch()))
;
s[i] = '\0';
if (c != EOF)//不管所读取的是单个小写字母,还是一个小写字母组成的字符串
//为了确定所读取内容的准确性,都需要多读取一个字符,然后将其“压回”栈中
ungetch(c);
if (strlen(s) > 1)
return Name;
else
return c;
}
测试结果:
错误之处:结尾处的
return c;
是不正确的,因为此时c是程序为了确定字符串完整性所多读取的一个字符。而在其之前的那个字符才是执行相应操作的字符标志。
所以修改为
return s[–i];
测试结果: