C语言编写的简单计算器程序

2023-11-11

这两天在看一个C语言写的计算器程序,做了不少的功夫,跟着作者一步步的进行完善,了解了许多细节性的东西,在此自己做个总结,加深自己对程序的印象,也算是梳理。

在该计算器程序,能进行加减乘除、sin、cos、exp等操作,同时能进行数值保存功能。而该计算器使用逆波兰表示法。即所有运算符都跟在操作数的后面,比如下列表达式:
(1 - 2) * (4 + 5)采用逆波兰表示法表示为:1 2 - 4 5 + *
逆波兰表达法中不需要圆括号,只要知道每个运算符需要几个操作数就不会引起歧义。

计算器程序实现很简单,具体原理如下:

while/* 下一个运算符或操作数不是文件结束指示符 */if/* 是数 *//* 将该数压入到栈中 */
    else if/* 是运算符 *//* 弹出所需数目的操作数 */
        /* 执行运算 */
        /* 将结果压入到栈中 */
    else if/* 是换行符 *//* 弹出并打印栈顶的值 */
    else
        /* 出错 */

在程序设计中,使用模块化思想,getop函数来进行读入,该函数返回一个标识,用来标识读入的是什么类型。主循环体中根据该标识执行相应的动作。

以下是该程序: (我将所有函数和变量放在同一文件)

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

#define MAXOP 100
#define NUMBER '0'   //标识读入的是数字
#define NAME 'n'     //标识读入的是字符串(函数名或非法字符串)
#define ALPHA 26     
int getop(char []);   
void push (double);  //压栈
double pop(void);    //出栈
void clear(void);    //清空栈
void mathfnc(char []);   //执行相应的数学函数sin、cos、exp等

int main(void)
{
    int type;
    int i, var = 0;
    double op1, op2,v;
    char s[MAXOP];
    double variable[ALPHA];

    for (i = 0; i < ALPHA; i++)    //初始化用于保存数值的变量数组
        variable[i] = 0.0;

    while ((type = getop(s)) != EOF)    //读取输入
    {
        switch (type)
        {
            case NUMBER:
                push (atof(s));
                break;
            case NAME:
                mathfnc(s);
                break;
            case '+':
                push (pop() + pop());
                break;
            case '*':
                push (pop() * pop());
                break;
            case '-':
                op2 = pop();
                push (pop() - op2);
                break;
            case '/':
                op2 = pop();
                if (op2 != 0.0)
                    push (pop() / op2);
                else
                    printf ("error: zero divisor\n");
                break;
            case '%':
                op2 = pop();
                if (op2 != 0.0)
                    push (fmod(pop(), op2));
                else
                    printf ("error: zero divisor\n");
                break;
            case '?':    //打印栈顶元素
                op2 = pop();
                printf ("\t%.8g\n", op2);
                push (op2);
                break;
            case '=':   //保存数值
                pop();
                if (var >= 'A' && var <= 'Z')
                    variable[var - 'A'] = pop();
                else
                    printf ("error: no variable name\n");
                break;
            case 'c':
                clear();
                break;
            case 'd':   //复制栈顶元素
                op2 = pop();
                push(op2);
                push(op2);
                break;
            case 's':   //交换栈元素
                op1 = pop();
                op2 = pop();
                push(op1);
                push(op2);
            case '\n':
                v = pop();   //v保存最后的一次结果
                printf ("\t%.8g\n", v);
                break;
            default:
                if (type >= 'A' && type <= 'Z')
                    push(variable[type - 'A']);
                else if (type == '@')   //输入的字符@表示最近一次结果值 
                    push(v);
                else
                    printf ("error: unknown command %s\n", s);
                break;
        }
        var = type;  
    }
    return 0;
}

/* ----------------------------------------------------------- */

#define MAXVAL 100

int sp = 0;     //标识栈顶
double val[MAXVAL];

void push(double f)
{
    if (sp < MAXVAL)
        val[sp++] = f;
    else
        printf ("error: stack full, can't push %g\n", f);
}

double pop(void)
{
    if (sp > 0)
        return val[--sp];
    else
    {
        printf ("error: statck empty\n");
        return 0.0;
    } 
}

void clear(void)
{
    sp = 0;
}

void mathfnc (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();
        push (pow(pop(), op2));
    }
    else
        printf ("error: %s not supported\n", s);
}

/* ----------------------------------------------------------- */

#include <ctype.h>

int getch(void);
void ungetch(int);

int getop(char s[])
{
    int i, c;
    while ((s[0] = c = getch()) == ' ' || c == '\t')  //过滤开头的空白字符
        ;
    s[1] = '\0';

    i = 0;

    if (islower(c))   //判断是否为小写字母,也即读取由小写字母组成的字符串
    {
        while (islower(s[++i] = c = getch()))
            ;
        s[i] = '\0';

        if (c != EOF)
            ungetch(c);
        if (strlen (s) > 1)
            return NAME;
        else 
            return c;
    }

    if (!isdigit(c) && c != '.' && c != '-')
        return c;

    if (c == '-')     //用于判断是负数还是减操作          
    { 
        if (isdigit(c = getch()) || c == '.')  
            s[++i] = c;
        else
        {
            if (c != EOF)
                ungetch(c);
            return '-';
        }
    }

    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);

    return NUMBER;
}


/* ----------------------------------------------------------- */
/*
 * 引用以下两个函数是因为:程序不能确定它已经读入的输入是否足够 *
 * 除非超前多读入一些输入,在本程序中,读入一些字符合成一个数字 *
 * 所以在看到第一个非数字字符之前,已经读入的数的完整性是不能确定的
 * 由于程序要超前读入一个字符,这样就导致最后又一个字符不属于当前所要读入的数 
 */

#define BUFSIZE 100
char buf[BUFSIZE];
int bufp = 0;

int getch(void)  
{
    return (bufp > 0) ? buf[--bufp] : getchar();
}

void ungetch (int c)
{
    if (bufp >= BUFSIZE)
        printf ("ungetch: too many characters\n");
    else
        buf[bufp++] = c;
}

该程序虽然简单,但是还是存在一些小小的问题,比如没有数据时进行pop的话,会打印栈中无数据同时返回数值0.0,在循环体中许多执行操作会将该数值保存到栈中,之后打印该值,用户体验度比较差。程序设计方面,模块化设计使得该程序容易增加功能而不影响其他模块,比如增加一些数学函数处理,在mathfnc函数中去添加,或增加一些运算符操作,可以在主循环体中增加。

总之,这次学习还是颇有收获。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言编写的简单计算器程序 的相关文章

  • Android中解决第三方库重复引用的问题

    如果app中引入了一个新的第三方库 并且这个新库中引入了原本已经引入的另一个库 结果导致重复引用 编译就会报错 如何解决呢 方法是使用exclude排除重复的库 举例 假设新引入的第三方库是 com xiboliya mylib netto
  • JAVA中的类

    一 什么是类 概念 类就是某些具备某些共同特征的实体的集合 它是一种抽象的数据类型 它是对所具有相同特征实体的抽象 在面向对象的程序设计语言中 类是对一类 事物 的属性与行为的抽象 类可以理解为一个模板 它描述一类对象的行为和状态 举个例子
  • 【Linux安全】chattr命令锁定账户敏感文件

    有时候你发现用root权限都不能修改某个文件 大部分原因是曾经用chattr命令锁定该文件了 chattr命令的作用很大 其中一些功能是由Linux内核版本来支持的 不过现在生产绝大部分跑的linux系统都是2 6以上内核了 通过chatt
  • jQuery 入门教程(9):终止动画

    jQuery的使用stop 方法在动画结束之前停止动画 基本语法如下 selector stop stopAll goToEnd 可选参数stopAll 指明是否同时清除 动画队列 缺省为false 意味着只停止当前活动的动画 之后的动画则
  • retrofit合理的处理response

    OKHttp retrofit 有时候使用起来确实会受到一些局限 比如 处理response的加解密 处理response的返回的字段与本地封装的不一样 又不能改本地的字段 所以需要对返回的JSON进一步处理 别名的方式 处理respons
  • 【mysql】云服务器被攻击,数据库以及数据都被删除如何通过binlog日志恢复

    前言 小编买了一台阿里云服务器 然后通过docker 部署了mysql 然后用了一段时间突然发现数据都没有了 然后就排查问题 发现是被攻击了 如下图 you must pay 0 26BTC 怒了 好多钱呢 如果有同样的问题 可以参考此博客

随机推荐

  • MFC实现socket网络通信--主机与服务器之间传送数据

    MFC实现socket网络通信 模拟主机与服务器之间传送数据 MFC实现socket网络通信 1 新建MFC应用程序 2 创建服务端窗口界面 3 写服务器代码 4 创建客户端窗口界面 5 客户端代码部分 6 开始调试 7 小结 MFC实现s
  • smallworld bm 配为ldap授权后授权界面中无法显示设计权限,需要修改config_local_and_ldap.xml配置文件

    config local and ldap xml配置文件增加相应配置
  • Sequel Pro导出关系图,可视化你的数据库

    教程链接 https nicolaswidart com blog exporting relations diagram from sequel pro 简易步骤 使用homebrew安装 brew install graphviz Se
  • 【网络通信】Netty面试专题之十大考问

    1 BIO NIO 和 AIO 的区别 BIO 一个连接一个线程 客户端有连接请求时服务器端就需要启动一个线程进行处理 线程开销大 伪异步 IO 将请求连接放入线程池 一对多 但线程还是很宝贵的资源 NIO 一个请求一个线程 但客户端发送的
  • 你知道this.$options吗?(Vue)

    题记 我们在Vue项目中会有很多情况下需要用到this options 所以接下来我们介绍几个场景会用到 options 我们想第一个问题当我们在template经常使用filter 那么你可以直接在methods里边用过滤器吗 我们在表单
  • GC 的三种基本实现方式

    GC 的三种基本实现方式 参考资料 代码的未来 作者 日 松本行弘 由于并非本人原著 我只是个 搬运工 SO 未经本人允许请尽情转载 另外个人像说明一下这里所说的GC指泛指垃圾回收机制 而单指Java或其他某种特定语言中的GC 可能具体语言
  • 【PTA】7-6 整除光棍

    7 6 整除光棍 这里所谓的 光棍 并不是指单身汪啦 说的是全部由1组成的数字 比如1 11 111 1111等 传说任何一个光棍都能被一个不以5结尾的奇数整除 比如 111111就可以被13整除 现在 你的程序要读入一个整数x 这个整数一
  • 设计模式-day02

    4 创建型模式 4 2 工厂模式 4 2 1 概述 需求 设计一个咖啡店点餐系统 设计一个咖啡类 Coffee 并定义其两个子类 美式咖啡 AmericanCoffee 和拿铁咖啡 LatteCoffee 再设计一个咖啡店类 CoffeeS
  • Ubuntu虚拟机和Windows实现文件拖拽复制粘贴

    方法 安装vm tools 1 在Ubuntu内部鼠标右键打开终端 2 更新apt get 一般新装的系统都需要更新apt get sudo apt get update ps 若无法更新 可以试着换一下镜像站 Ubuntu系统配置镜像站
  • Python基础系列2——Numpy数值计算及分析

    文章目录 1 实验内容 2 实验过程 2 1 numpy数组的建立 索引 计算 统计等 2 2 利用numpy对数据集 iris data 进行分析 3 实验结论及注意事项 1 实验内容 1 numpy数组的建立 索引 计算 统计等 2 利
  • BIOS和BootLoader uboot

    BIOS BIOS是英文 Basic Input Output System 的缩略语 直译过来后中文名称就是 基本输入输出系统 其实 它是一组固化到计算机内主板上一个ROM芯片上的程序 它保存着计算机最重要的基本输入输出的程序 系统设置信
  • Java Sort方法

    Java的sort方法就是排序 而且排的是升序 你要想降序可以先获得升序的 然后倒过来或者你重新写比较器Comparator的接口就可以 一 sort 排序方法本身 这里讲的sort方法 都是以Arrays类里面的方法为准 因为很多类的so
  • STM32 端口复用学习

    一 STM32端口复用 1 端口复用定义 STM32有很多的内置外设 这些外设的外部引脚都是与GPIO复用 也就是说 一个GPIO如果可以复用为内置外设的功能引脚 那么当这个GPIO作为内置外设使用的时候 就叫做复用 2 作用 最大限度的利
  • vue 实现计时器组件

    vue 实现计时器组件 结果图 v if 和 v show 的区别 总结来说v if是在不断的销毁和重建 v show 只是改变 display 属性 元素依然存在 dom 中 v if 切换开销大 v show 初始化开销大 time v
  • 人称代词用法大全

    语言发明出来自然是要给人用的 所以跟人相关的词就特别多 划分的很细 我们提到某个具体的人一般就直接说名字 但有时是泛指 或者前面已经提过名字了 后面用个啥简称指代下就清楚了 这就需要代词 代词嘛顾名思义是一个代称 是指代某个人或者某类人 某
  • 搭建使用 VS 开发 Qt 项目的环境

    搭建使用 VS 开发 Qt 项目的环境 个人认为 使用 Qt 工具开发 Qt 项目是最好的方案 在开发的过程出现的 bug 会比较少一些 但是有些同伴可能对 VS 比较钟爱 而 VS 又有此功能 因此想采用 VS 进行开发 本文将本人搭建成
  • nacos源码启动找不到istio包

    现象 源码版本2 1 0 启动时 编译不通过 报错 找不到 istio mcp v1alpha1 MetadataOuterClass Metadata istio networking v1alpha3 ServiceEntryOuter
  • 算法:链表

    单链表 单链表是一种链式存取的数据结构 链表中的数据是以结点来表示的 每个结点存储两个数据 一是该结点本身的值 二是其指向的下一结点的下标 用e i 表示节点i的值 用ne i 表示结点i指向的下一结点的坐标 head表示头结点的下标 id
  • 网关系统架构

    目录 一 API网关业务域 1 业务域 2 统一接入 3 安全防护 4 流量管控 5 协议转换 6 其他业务 1 接口文档管理 2 调试工具和示例 3 SDK自动生成能力 4 API增强 二 API网关核心指标 1 模型 2 安全性 3 高
  • C语言编写的简单计算器程序

    这两天在看一个C语言写的计算器程序 做了不少的功夫 跟着作者一步步的进行完善 了解了许多细节性的东西 在此自己做个总结 加深自己对程序的印象 也算是梳理 在该计算器程序 能进行加减乘除 sin cos exp等操作 同时能进行数值保存功能