简易计算器A-C++学习笔记

news/2025/3/20 0:06:22/文章来源:https://www.cnblogs.com/duraadream/p/18774398

学习了C++简易计算器的程序设计过程后,觉得有必要对其进行一个梳理,其中的知识点和思想还是很重要的。

OpenJudge上已经把所学内容浓缩成了两个题目,下面主要是对这两个题目的参考代码进行解析。

A

简单计算器之支持符号

总时间限制: 1000ms 内存限制: 65536kB

描述
计算器程序的输入是一个单词序列,如(1.5+4)*11

我们希望实现一个简单计算器,能够支持对加减乘除的运算,正如你在练习1中做过的那样。

但是这一次,我们不再限定输入是3个操作符,而是一个表达式, 表达式中包含括号的使用,比如(3-2.4)/(7+9)

除此之外,我们还希望在程序中增加对于{}的支持,另其作用与()一致,这样,{(4+5)*6/(3*4)}就是一个合法的表达式

输入
一个字符串表达式,能够保证所有的测试测试样例的表达式都是合理的,只包含+, -, *, /,(), {}以及数字,数字仍然都是double型的

输出
输出计算结果,计算结果保留小数点后6位,如果计算结果没有6位,也保留到六位后为0

样例输入
(3-2.4)/(7+3)

样例输出
0.060000

代码逐段分析:

#include <iostream>
#include <iomanip>
using namespace std;const char number = '8';
const char quit ='\n';inline void error(const string& s)
{throw runtime_error(s);
}

这一段是一些初始操作,包括定义表达式的部分分词的类型:numberquit。而error函数则抛出运行时的异常问题。

接下来是第一个类:分词类。

将表达式拆分为基本单元,每个单元就是一个分词。分词具有两方面的属性:类型和值,其中值只对数字有意义,其构造函数的初始化是很常见的。

class token
{
public:char kind;double value;token(char ch):kind(ch), value(0) {}token(char ch, double val):kind(ch), value(val) {}
};

仅有分词类是不够的,输入流的处理是本问题的第二个难点。输入流需要完成以下功能:读取输入,对不合理的输入进行putback操作。我们将输入流写成类,得到如下的代码:

class token_stream
{
public:token_stream();token get();void putback(token t);
private:token buffer;// 缓冲分词bool full;
};
token_stream::token_stream():full(false), buffer(0)    // no Token in buffer
{
}token_stream ts;

下面是对相关函数的具体实现:

  • putback()函数
void token_stream::putback(token t){if (full) error("putback() into a full buffer");buffer = t;       // copy t to bufferfull = true;      // buffer is now full
}
  • get()函数
token token_stream::get()
{if (full){full = false;return buffer;// 返回缓冲内容}char ch;cin >>noskipws>> ch;switch (ch){case quit:case '+':case '-':case '*':case '/':case '(':case ')':case '{':case '}':return token(ch);case '0': case '1': case '2': case '3': case '4': case '5':case '6': case '7': case '8': case '9':case '.':{cin.putback(ch);// 小数点放回缓冲区double val;cin >> val;return token(number, val);}default:error("bad token");return token(ch);}
}

下面是对表达式的计算。这里采用文法的方式进行运算,有兴趣可以阅读原书,重点是将表达式分解为基本单元,并利用其文法完成对表达式的计算。要用到以下三个函数:expression(),term(),primary()

double expression();
double term();
double primary();
//循环调用

下面是对其的实现:

  • expression()函数
double expression()
{double left = term();token t = ts.get();while (true){switch (t.kind){case '+':left += term();t = ts.get();break;case '-':left -= term();t = ts.get();break;default:ts.putback(t);return left;}}
}
  • term()函数
double term()
{double left = primary();token t = ts.get();while (true){switch (t.kind){case '*':left *= primary();t = ts.get();break;case '/':{double d = primary();if (d == 0) error("divide by zero");left /= d;t = ts.get();break;}default:ts.putback(t);return left;}}
}
  • primary()函数
double primary()
{token t = ts.get();while (true){switch (t.kind){case number:return t.value;case '(':{double d = expression();t = ts.get();if (t.kind != ')') error("')' expected");return d;}case '{':{double d = expression();t = ts.get();return d;}default:error("primary expected");return 0;}}
}

最后到了收尾阶段,在主函数里创建分词对象,并进行输入,解析,输出。

int main()
{try {double val = 0;while (cin) {token t = ts.get();if (t.kind == quit) {cout << fixed << setprecision(6) << val << endl;break;}else {ts.putback(t);val = expression();}}}catch (exception & e) {cerr << e.what() << endl;return 1;}catch (...) {cerr << "exception \n";return 2;}
}

根据try{}catch()尝试并处理抛出异常,这部分内容在后续的学习中会陆续补全。首先我们创建一个分词对象,利用输入流读入,当读到\n时,表达式结束,此时其值也计算完毕,输出即可,否则则将其存入缓冲区,进行表达式的计算。

程序完整的代码如下:

/*
Author:Zac.
Date:2025.3.20
Description:Homework3.A
*/
#include <iostream>
#include <iomanip>
using namespace std;const char number = '8';
const char quit ='\n';inline void error(const string& s)
{throw runtime_error(s);
}class token
{
public:char kind;double value;token(char ch):kind(ch), value(0) {}token(char ch, double val):kind(ch), value(val) {}
};class token_stream
{
public:token_stream();token get();void putback(token t);
private:token buffer;bool full;
};token_stream::token_stream():full(false), buffer(0)    // no Token in buffer
{}token_stream ts;void token_stream::putback(token t){if (full) error("putback() into a full buffer");buffer = t;       // copy t to bufferfull = true;      // buffer is now full
}token token_stream::get()
{if (full){full = false;return buffer;}char ch;cin >>noskipws>> ch;switch (ch){case quit:case '+':case '-':case '*':case '/':case '(':case ')':case '{':case '}':return token(ch);case '0': case '1': case '2': case '3': case '4': case '5':case '6': case '7': case '8': case '9':case '.':{cin.putback(ch);double val;cin >> val;return token(number, val);}default:error("bad token");return token(ch);}
}double expression();
double term();
double primary();
//循环调用double expression()
{double left = term();token t = ts.get();while (true){switch (t.kind){case '+':left += term();t = ts.get();break;case '-':left -= term();t = ts.get();break;default:ts.putback(t);return left;}}
}double term()
{double left = primary();token t = ts.get();while (true){switch (t.kind){case '*':left *= primary();t = ts.get();break;case '/':{double d = primary();if (d == 0) error("divide by zero");left /= d;t = ts.get();break;}default:ts.putback(t);return left;}}
}double primary()
{token t = ts.get();while (true){switch (t.kind){case number:return t.value;case '(':{double d = expression();t = ts.get();if (t.kind != ')') error("')' expected");return d;}case '{':{double d = expression();t = ts.get();return d;}default:error("primary expected");return 0;}}
}int main()
{try {double val = 0;while (cin) {token t = ts.get();if (t.kind == quit) {cout << fixed << setprecision(6) << val << endl;break;}else {ts.putback(t);val = expression();}}}catch (exception & e) {cerr << e.what() << endl;return 1;}catch (...) {cerr << "exception \n";return 2;}
}

这样,我们完成了对简易计算器的第一版实现,在命令行里输入表达式,即可以进行计算。在第二版中,我们将实现支持变量和常量的简易计算器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/901794.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

drm study

学习过程 0319:对于任何驱动来说,buffer是最重要的,知道了buffer的创建使用这个驱动就会一半了;现在感觉是一个无头苍蝇,感觉非常复杂:数据结构非常多,之间的关系也非常复杂;不过没关系,先研究buffer通路;可以看见应用层对mmap写入的hello world,驱动中vkms_obj->…

pcie 简介及引脚定义

随着现代处理器技术的发展,在互连领域中,使用高速差分总线替代并行总线是大势所趋。与单端并行信号相比,高速差分信号可以使用更高的时钟频率,从而使用更少的信号线,完成之前需要许多单端并行数据信号才能达到的总线带宽。 PCI总线使用并行总线结构,在同一条总线上的所有…

C++ 基础(1)

0x01 第一个C++程序 #include <iostream>int main() {std::cout << "Hello World!\n"; } // std::cout 向控制台输出内容的指令 // << 输出的运算符 // "" 字符串内容的边界符 // \n 输出换行 // Hello World 输出字符…

在ubuntu系统下与开发板连接问题记录

对我所遇到的问题以及解决方法进行简单的记录在开发板与ubuntu(非虚拟机)连接之后使用lsmod查看是否连接lsusb 我的显示如下:如果可以看到自己的USB设备 那么就说明你已经安装了驱动 如果没有 请安装你的串口对应的驱动 我的驱动是CH340 没有安装的朋友可以去下面网站进行…

maven为什么发生依赖冲突?怎么解决依赖冲突?

maven为什么发生依赖冲突?怎么解决依赖冲突? 我们在开发的时候,偶尔会遇到依赖冲突的时候,一般都是NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError。打开搜索框又发现有这个类,明明就是引入进来了,就是找不到,让人头疼 1. 依赖冲突场景 在maven中依赖…

unstructured

unstructured 是一个开源的 Python 库,专门用于处理非结构化数据,如从 PDF、Word 文档、HTML 文件等中提取文本内容,并将其转换为结构化格式(1)安装依赖库pip install unstructured使用textfrom unstructured.partition.auto import partitionfilename = "a.txt"…

idea如何激活到2099年

前言 最近发现idea如何激活使用的问题、 网络上各种都是骗关注加各种公众号的最后也没有解决问题,下面分享一下我的激活方法是如何激活到2099年。目前适用于idea的所有版本。我以最新的ideaIU-2024.3为例。 一去官网上下载idea 官网下载地址:https://www.jetbrains.com.cn/id…

202108032324 - kafka的生产流程

由上图可以看出:KafkaProducer有两个基本线程: 主线程: 负责消息创建,拦截器,序列化器,分区器等操作,并将消息追加到消息收集器 RecoderAccumulator中; 消息收集器RecoderAccumulator为每个分区都维护了一个Deque<ProducerBatch> 类型的双端队列。 ProducerBatch…

Cython二进制逆向系列(三)运算符

在这篇文章里,我们会讨论Cython是如何处理运算符的(数学运算符、位运算符、in/not in 运算符、 ==运算符与逻辑运算符)。总的来叔其中大部分是调用虚拟机api来实现的。Cython二进制逆向系列(三)运算符在开始前,先给出本文用到的py源代码 def test1(x, y):# 数学运算符a…

QOJ 9785 Shrooks

曼哈顿距离转切比雪夫距离,对限制的贪心满足,扩宽限制,简化信息首先考虑限制的形式:对于两点 \((x_1, y_1), (x_2, y_2)\),要求 \(|x_1 - x_2| + |y_1 - y_2| \le n\)。 但是这个式子与 \(x_1, x_2, y_1, y_2\) 这 \(4\) 个值都强相关,且这里的绝对值也并不好拆开处理。 …

golang的GC机制

一、垃圾回收 什么是垃圾回收?垃圾回收(GC,garbage collection)是自动内存管理的一种形式,通常由垃圾收集器收集并适时回收或重用不再被对象占用的内存,比如众所周知的Java语言就能很好的支持GC。后起之秀——Go语言也同样支持垃圾回收,它使得Go程序员在编写程序的时候不…

fastjson漏洞复现(fastjson 1.2.47)

原理: Fastjson 的 @type 字段允许反序列化时动态指定类,攻击者可以通过指定带有危险行为的类,并结合 RMI/LDAP 远程加载恶意类,从而实现命令执行。开启vulhub靶场//如何验证是否存在fastjson反序列化漏洞 有json数据传输的地方可能存在 可以尝试使用dnslog带外但是不知道…