1 命令行计算器(harib27a)
这一章我们要编写一些“高级的应用程序”,不过其实也没那么高级,说不定还是昨天那个invader.hrb比较高级呢,因此大家可别抱太大的期望哦(笑)。
那到底做点什么样的应用程序好呢?现在我们的系统中已经有noodle.hrb可以帮我们做泡面,还有invader.hrb可以用来玩游戏,应该再找一些其他能派上用场的功能……于是笔者想到了计算器。
Windows中附带了一个计算器软件,不过做那样一个好看的计算器太麻烦了,我们就做一个在命令行中输入算式来进行计算的应用程序吧。
■■■■■
我们来看程序。
calc.c
#include "apilib.h"
#include <stdio.h> /* sprintf */
#define INVALID -0x7fffffff
int strtol(char *s, char **endp, int base); /*标准函数(stdlib.h) */
char *skipspace(char *p);
int getnum(char **pp, int priority);
void HariMain(void)
{
int i;
char s[30], *p;
api_cmdline(s, 30);
for (p = s; *p > ' '; p++) { } /*一直读到空格为止*/
i = getnum(&p, 9);
if (i == INVALID) {
api_putstr0("error!\n");
} else {
sprintf(s, "= %d = 0x%x\n", i, i);
api_putstr0(s);
}
api_end();
}
char *skipspace(char *p)
{
for (; *p == ' '; p++) { } /*将空格跳过去*/
return p;
}
int getnum(char **pp, int priority)
{
char *p = *pp;
int i = INVALID, j;
p = skipspace(p);
/*单项运算符*/
if (*p == '+') {
p = skipspace(p + 1);
i = getnum(&p, 0);
} else if (*p == '-') {
p = skipspace(p + 1);
i = getnum(&p, 0);
if (i != INVALID) {
i = - i;
}
} else if (*p == '~') {
p = skipspace(p + 1);
i = getnum(&p, 0);
if (i != INVALID) {
i = ~i;
}
} else if (*p == '(') { /*括号*/
p = skipspace(p + 1);
i = getnum(&p, 9);
if (*p == ')') {
p = skipspace(p + 1);
} else {
i = INVALID;
}
} else if ('0' <= *p && *p <= '9') { /*数值*/
i = strtol(p, &p, 0);
} else { /*错误 */
i = INVALID;
}
/*二项运算符*/
for (;;) {
if (i == INVALID) {
break;
}
p = skipspace(p);
if (*p == '+' && priority > 2) {
p = skipspace(p + 1);
j = getnum(&p, 2);
if (j != INVALID) {
i += j;
} else {
i = INVALID;
}
} else if (*p == '-' && priority > 2) {
p = skipspace(p + 1);
j = getnum(&p, 2);
if (j != INVALID) {
i -= j;
} else {
i = INVALID;
}
} else if (*p == '*' && priority > 1) {
p = skipspace(p + 1);
j = getnum(&p, 1);
if (j != INVALID) {
i *= j;
} else {
i = INVALID;
}
} else if (*p == '/' && priority > 1) {
p = skipspace(p + 1);
j = getnum(&p, 1);
if (j != INVALID && j != 0) {
i /= j;
} else {
i = INVALID;
}
} else if (*p == '%' && priority > 1) {
p = skipspace(p + 1);
j = getnum(&p, 1);
if (j != INVALID && j != 0) {
i %= j;
} else {
i = INVALID;
}
} else if (*p == '<' && p[1] == '<' && priority > 3) {
p = skipspace(p + 2);
j = getnum(&p, 3);
if (j != INVALID && j != 0) {
i <<= j;
} else {
i = INVALID;
}
} else if (*p == '>' && p[1] == '>' && priority > 3) {
p = skipspace(p + 2);
j = getnum(&p, 3);
if (j != INVALID && j != 0) {
i >>= j;
} else {
i = INVALID;
}
} else if (*p == '&' && priority > 4) {
p = skipspace(p + 1);
j = getnum(&p, 4);
if (j != INVALID) {
i &= j;
} else {
i = INVALID;
}
} else if (*p == '^' && priority > 5) {
p = skipspace(p + 1);
j = getnum(&p, 5);
if (j != INVALID) {
i ^= j;
} else {
i = INVALID;
}
} else if (*p == '|' && priority > 6) {
p = skipspace(p + 1);
j = getnum(&p, 6);
if (j != INVALID) {
i |= j;
} else {
i = INVALID;
}
} else {
break;
}
}
p = skipspace(p);
*pp = p;
return i;
}
如果以前没有编写过类似的计算器程序的话,估计不太容易看懂上面这段程序。这段程序本来就已经偏离了“编写操作系统”这个主题,因此笔者也没打算逼着大家看懂,不过如果你对这段程序很感兴趣,不妨先运行一下玩玩看,然后再仔细阅读下面的讲解,一定能慢慢看明白的。
在这段程序中使用了strtol(s, endp, base)这样一个函数,这个函数的功能基本上和sprintf是相反的,它可以将字符串形式的数值转换为整数。字符串的地址由s指定,base表示进制,例如10代表十进制,16代表十六进制,0则代表自动识别(以0x开头则识别为十六进制)。
endp一般可以指定为0,也可以指定一个变量的地址,如果指定了地址,则函数会返回在转换字符串时所读取到的字符串末尾地址。字符串末尾地址是一个char 型的变量,这个变量的地址由endp指定,地址的地址,也就是一个char *型的变量。
strtol是一个只要include了就可以使用的标准函数,不过笔者在“纸娃娃系统”用的tolset中没有包含,因此在程序中直接进行了声明。
getnum中也使用了char **型的参数,和strtol一样,也是为了将当前解析到算式中的位置返回给相应的变量。
函数getnum的功能是将字符串形式的算式进行解释,并获取一个数值。除了进入字符串开始地址的变量地址外,还需要指定计算到哪个等级的运算符(“+”、“/”等用于表示各种运算的符号)。在HariMain中我们指定了9,这代表“无论多么低优先级的运算符,全部需要计算出来”的意思。
用作运算符的字符包括+ - / % & | ^ ~ << >> ( ),结果同时显示十进制和十六进制。不过计算都是以整数进行,比如:10 / 3 = 3,可以使用负数。计算的优先级和C语言的规定相同,因此1 + 2 3 + 4 = 11,如果单纯从左往右按顺序计算的话结果应该是13,但我们的计算器不会这样计算,当然,如果输入( 1 + 2 ) * 3 + 4的话就可以计算出13了哦。
由于使用了和C语言相同的语法规则,因此^是代表XOR运算的运算符,2^3可不是“2的3次方”的意思,而是“2 XOR 3”的意思哦。另外,和C语言一样,我们可以直接输入十六进制的数字进行计算,只要在数字前面加上0x就可以了。
■■■■■
我们来“make run”看看吧,关于使用方法,可以输入如 “calc 1+2”。
![]() | ![]() | |
尝试进行了各种计算 | 又尝试了更多种类的计算 |
怎么样,还挺有意思的吧?这样一来,一些比较简单的计算用“纸娃娃系统”就可以胜任了哦,可喜可贺。对了,calc.hrb只有1688字节哦,短小精悍吧!