使用C++完成以栈为基础的简易计算器,并使用Qt5制作可视化界面

2023-11-10

使用C++完成以栈为基础的简易计算器,并使用Qt5制作可视化界面

一.计算器内部逻辑构造:

在完成计算器时选用栈数据结构(自己编写的和标准模板库中的都可),要求支持加减乘除和逻辑与或非运算,可计算整数和小数。这一部分有很多思路都可以实现,我的写法是将整个步骤分为表达式抽离,出入栈操作和运算符运算三个步骤。

1.表达式抽离

首先,在没有做出交互界面的情况下我们假定用户以英文键盘输入所要计算的表达式,这时你就会得到一个字符串,为了将字符串中每一个数字或运算符进行分割抽离,我们可以选用一个vector容器储存单独抽离出的每一个数字或运算符,具体解决思路如下:
1.设置一个储存字符串类型的vector v;并设置一个用于暂时储存数字的字符串number。
2.开始对字符串进行抽离,总体原则如下:
使用循环对每个字符进行判断,如果是数字,就将其放进number中暂时储存起来,接着对下一个字符进行判断:如果下一个字符是依旧是数字,就说明用户输入的数字大于9,继续将他放入number中即可。如果下一个字符不是数字,就需要将目前number中储存的数字放进v中,并将number清空。(注意:因为要实现小数运算,所以当字符不是数字时应该判断是否是小数点,如果是小数点就应该将其放入number中,算作这个数字的一部分。)如果判断不是小数点,将其放入v中即可。(注意,对于&&与 || 两个运算符,为了处理方便,我们只将&和 | 放入v中,同时,因为这两个符号在字符串中占两个位置,所以在处理这两个符号后应前推一个字符。)

补充特例:取反运算符“ !”;对于之前所有运算符和数字,我们将其抽离后都是放入栈中运算(具体后面会讲到),但是对于“!”运算符,因为他是单目运算符,所以我不想把他放入栈中(我也不知道当时怎么想的,可能脑子抽了),所以在当字符不是数字时需要判断当前字符不是”!“时才可按照上述原则处理符号。当检测到这个字符是”!“时应该怎么办呢?我们可以对可能出现的情况进行分类:
1.如果”!“后是数字,那么就单独设置一个字符串s,按照之前的处理原则,将整个数字放入s中,并将其转变为数字,将这个数字进行取反运算,并将得到的结果放入number中,就相当于将”!xxx“整体看作了0或1,当作数字抽离。
2.如果”!“后是括号,我们则需要计算括号中整体的运算结果后再与”!“进行运算,对于这一步,我们需要找到”!“后左括号所对应控制的右括号,并将括号以及其内容整体进行抽离,这时可以设置一个字符串对”!“所支配的表达式进行储存,考虑到可能出现如!((1))等多层括号嵌套的情况,我们可以设置参数a和b来分别储存左右括号数,当括号数相同时进行抽离。这时,我们就已经得到了”!"所控制的内容,因为我们不知道这一部分内容的复杂度,所以我们可以将其放入evaluate()(后面会看到)中重新对这一部分进行抽离,出入栈并进行运算得到结果(这里采用了递归的方法,减少了需要我们处理的部分)最后将所得结果进行取反运算,转化为0或1放入number即可。
至此,我们的抽离运算已经完成,下面贴上代码:

vector<string> split(const string& expression)//处理储存表达式
{
	vector<string> v;
	string number;
	for (int i = 0; i < expression.length(); i++)
	{
		if (isdigit(expression[i]))
			number.append(1, expression[i]);
		else
		{
			if (number.size() > 0 && (expression[i] != '!'))
			{
				if (!isspace(expression[i]) && (expression[i] == '.'))//处理小数点
					number.append(1, expression[i]);
				else
				{
					v.push_back(number);
					number.erase();
				}
			}
			if (!isspace(expression[i]) && (expression[i] == '|'))
			{
				string a;
				a.append(1, expression[i]);
				v.push_back(a);
				i++;
			}
			if (!isspace(expression[i]) && (expression[i] == '&'))
			{
				string b;
				b.append(1, expression[i]);
				v.push_back(b);
				i++;
			}
			if (!isspace(expression[i]) && (expression[i] != '.') && (expression[i] != '!') && (expression[i] != '|') && (expression[i] != '&'))
			{
				string s; s.append(1, expression[i]);
				v.push_back(s);
			}
			if (!isspace(expression[i]) && (expression[i] == '!'))
			{
				if (isdigit(expression[i + 1]))
				{
					string s;
					while (expression[i + 1] == '.' || isdigit(expression[i + 1]))
					{
						s.append(1, expression[i + 1]);
						i++;
					}
					int a = !atof(s.c_str());
					if (a == 0)
						number.append(1, '0');
					else if (a == 1)
						number.append(1, '1');
				}
				if (expression[i + 1] == '(')
				{
					string s;
					int head = 1;
					int tail = 0;
					s.append(1, '(');
					i++;
					while (tail != head)
					{
						s.append(1, expression[i + 1]);
						if (expression[i + 1] == '(')
							head++;
						else if (expression[i + 1] == ')')
							tail++;
						i++;
					}
					int a = !(evaluate(s));
					if (a == 0)
						number.append(1, '0');
					else if (a == 1)
						number.append(1, '1');
				}
			}
		}
	}

	if (number.size() > 0)
		v.push_back(number);
	return v;
}

2.对抽离后的表达式进行入栈并计算

这一部分很多数据结构的书上都会讲,这里就不再多说,只需要注意各个运算符的逻辑顺序即可,我这里将出入栈操作与运算操作分开写为两个函数compute和evaluate,其中evaluate包含抽离函数split和计算函数compute,以一个字符串为参数,最终只需调用evaluate函数即可。最后输出结果为double类型。
下面直接贴上代码:

double evaluate(const string& expression)//进行栈处理
{
	LStack<double> operandStack;
	LStack<char> operatorStack;
	vector<string> evaluate_posture = split(expression);
	for (int i = 0; i < evaluate_posture.size(); i++)
	{

		if (evaluate_posture[i][0] == '+' || evaluate_posture[i][0] == '-')
		{
			while (!operatorStack.empty() && (operatorStack.get_top() == '+' || operatorStack.get_top() == '-' || operatorStack.get_top() == '*' || operatorStack.get_top() == '/' || operatorStack.get_top() == '|' || operatorStack.get_top() == '&'))
			{
				compute(operandStack, operatorStack); ;
			}
			operatorStack.Push(evaluate_posture[i][0]);
		}
		else if (evaluate_posture[i][0] == '*' || evaluate_posture[i][0] == '/')
		{
			while (!operatorStack.empty() && (operatorStack.get_top() == '*' || operatorStack.get_top() == '/' || operatorStack.get_top() == '|' || operatorStack.get_top() == '&'))
			{
				compute(operandStack, operatorStack);
			}
			operatorStack.Push(evaluate_posture[i][0]);
		}
		else if (evaluate_posture[i][0] == '|')
		{
			while (!operatorStack.empty() && (operatorStack.get_top() == '|' || operatorStack.get_top() == '&'))
			{
				compute(operandStack, operatorStack);
			}
			operatorStack.Push(evaluate_posture[i][0]);
		}
		else if (evaluate_posture[i][0] == '&')
		{
			while (!operatorStack.empty() && (operatorStack.get_top() == '&'))
			{
				compute(operandStack, operatorStack);
			}
			operatorStack.Push(evaluate_posture[i][0]);
		}
		else if (evaluate_posture[i][0] == '(')
		{
			operatorStack.Push('(');
		}
		else if (evaluate_posture[i][0] == ')')
		{
			while (operatorStack.get_top() != '(')
			{
				compute(operandStack, operatorStack);
			}
			operatorStack.pop();
		}
		else
		{
			operandStack.Push(atof(evaluate_posture[i].c_str()));
		}
	}
	while (!operatorStack.empty())
	{
		compute(operandStack, operatorStack);
	}
	return operandStack.pop();
}

下面是计算部分的代码:

void compute(LStack<double>& operandStack, LStack<char>& operatorStack)//运算符分别进行计算
{
	char x = operatorStack.pop();
	double a = operandStack.pop();
	double b = operandStack.pop();
	if (x == '+')
		operandStack.Push(b + a);
	if (x == '-')
		operandStack.Push(b - a);
	if (x == '*')
		operandStack.Push(b * a);
	if (x == '|')
	{
		operandStack.Push(a || b);
	}
	if (x == '&')
	{
		operandStack.Push(a && b);
	}
	if (x == '/')
	{
		if (a == 0)
			cout << "错误,零不能作分母" << endl;
		else
			operandStack.Push(b / a);
	}
}

到这里,计算器内部的逻辑结构基本上就已经实现,经过测试,此计算器可以完整的计算一些简单运算,下面给出两个典型例子:
在这里插入图片描述在这里插入图片描述
有一点需要注意,我用的栈是自己写的,和标准模板库中的有一点不同,那就是我的pop函数会返回栈顶元素,标准模板库中的却不会,如果你使用的是标准模板库中的栈类型,需要对这一点进行更改。

使用Qt做出图形界面

这一点相信主要是工大的可怜孩子们有这样的需求,我也没有系统的学习Qt,主要是看了几节视频和借鉴同学写的博客,至于基础的操作,我觉得下面这篇博客写的较为清楚,大家可以直接去看这一篇,我这里主要是对制作过程中遇到的一些问题(坑)进行介绍。
下面先贴上博客链接:https://blog.csdn.net/yogur_father/article/details/106222926?utm_source=app
下面介绍一些我遇到的问题,如果你也遇到了和我同样的问题,希望可以为你提供一些帮助。

1.添加背景图片

添加背景图片的方法,在上面贴出的博客里已经有了介绍,简单来说就是在设计界面使用styleSheet进行背景图片的添加,在添加时,background-image和border-image都会改变原图片的尺寸,只有image不会,这里我选用的是image。需要注意的是,对于Qwidget类创建的主窗口进行操作时,所添加的图片在运行后的计算器界面中并没有出现,当我将按键设置为透明后,反倒是点击按键会显示添加的图片,对于这个问题的解决,可以从设计界面添加一个Frame覆盖住整个窗口,对这个Frame进行设置背景图片,然后单击右键选择放到后面,将其置于按钮下方即可。

2.按键透明化

有了漂亮的背景,怎么能被丑丑的按键挡住呢,要设置透明的按键,主要有两步:
1.对于选中的按钮,将左下角属性中的Flat选中
在这里插入图片描述
2.右键单击选中的按钮->改变样式表->在文本框中输入:background:transparent;即可
在这里插入图片描述
同样的,可以使用styleSheet改变字体的大小。

3.弹窗报错机制的实现

之前我们的目的是做出可交互的图形界面,那么就要考虑到一些错误输入的情况,理想的做法是:每当用户进行错误的输入时,我们可以设置一个弹窗来提醒用户。由于并没有深入的学习Qt,我在经过查阅后选择了最简单的实现需求的方法,具体说明如下:
在Qt中内置了一个QMessageBox的类型,它的作用就是创造一个警告弹窗,你可以自行选择弹窗中的内容,对于我这种初学者来说这个类真的超赞,可以说用最少的代码量实现了我想要的功能具体实现如下:

#include<QMessageBox>//包含对应头文件
     QMessageBox  warnning;  //创造对象   
     warnning.setText("请放心!");    //设置提醒内容
     warnning.exec();

到了这里,我们已经学会了如何创造一个弹窗,你只需要在用户进行错误输入的时候进行提醒即可,下面举一个例子:当用户没有输入表达式直接点击“=”时进行提醒:
在这里插入图片描述当用户进行错误输入时就会有弹窗提醒:
在这里插入图片描述当然,可能出现的异常输入有很多,你可以对每一种错误输入设置单独的提醒,这里就不再多说。

4.小数输出

等号的实现思路与注意事项在我先前贴出的那个博客里已经提到,这里需要注意的是我们的计算器可以计算小数,那么小数就要有保留位数,设置输出位数的代码如下:

       string abc=sss.toStdString();//将Qstring变为string
        double aaa=evaluate(abc);//得到计算结果
        res=QString::number(aaa,10,4);//将浮点数转化为Qstring并设置为小数点后四位。
        ui->textEdit_2->setText(res);//显示结果

5.exe打包

好了,现在我们的计算器就已经基本上完成并且可以运行了,不出意外的话,我想你一定会想将整个程序打包,得到一个可以单独运行的exe文件,巧了,我也一样,对于这一过程的实现过程,下面这篇博客有详细的介绍,我就不在多说,下面是链接:
https://blog.csdn.net/windsnow1/article/details/78004265?utm_source=app

到这里,整篇教程应该就已经完成啦(抹一把辛酸泪),如果你觉得对你有帮助的话,就点个赞吧(◍˃̶ᗜ˂̶◍)✩

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

使用C++完成以栈为基础的简易计算器,并使用Qt5制作可视化界面 的相关文章

随机推荐

  • LINUX-LVM简单配置

    1 在编辑设置中为虚拟机添加另一块新硬盘 2 fdisk dev sdb gt n新建分区 gt p选择默认主分区 gt 回车 gt 回车 gt 回车 gt t为新建分区分配id gt 8e gt 回车 3 pvcreate dev sdb
  • 字符串有关习题

    0 请问下面代码有没有毛病 为什么 1 gt gt gt input I love FishC com 2 gt gt gt print input 3 I love FishC com 答 有问题 给变量命名要避免与内置函数名字冲突 1
  • 安装老版本flash - 解决”正尝试安装的adobe flash player不是最新版本“的办法

    安装Flash player时提示 正尝试安装的adobe flash player不是最新版本 解决的办法是在运行中输入regedit 在注册表中找到 HKEY LOCAL MACHINE SOFTWARE Macromedia Flas
  • c语言中关于时间的函数

    本文从介绍基础概念入手 探讨了在C C 中对日期和时间操作所用到的数据结构和函数 并对计时 时间的获取 时间的计算和显示格式等方面进行了阐述 本文还通过大量的实例向你展示了time h头文件中声明的各种函数和数据结构的详细使用方法 关键字
  • 抓蓝牙数据包_蓝牙协议分析工具Wireshark/Frontline/Ellisys的使用

    一 声明 本专栏文章我们会以连载的方式持续更新 本专栏计划更新内容如下 第一篇 蓝牙综合介绍 主要介绍蓝牙的一些概念 产生背景 发展轨迹 市面蓝牙介绍 以及蓝牙开发板介绍 第二篇 Transport层介绍 主要介绍蓝牙协议栈跟蓝牙芯片之前的
  • JAVA String.intern()方法

    感谢原作者的分享 转载只为方便知识点 原文地址 http www runoob com java java string intern html 莫洛的笔记 尽管在输出中调用intern方法并没有什么效果 但是实际上后台这个方法会做一系列的
  • Java面试准备——计算机网络

    计算机网络相关面试重点整理 本文学习自GitHub上的JavaGuide项目 感谢大佬的资源 此处为自我学习与整理 原项目链接 JavaGuide OSI和TCP IP各层的结构和功能 都有哪些协议 我们平时学习计算机网络使用五层结构 比较
  • openlayers 3 之 getPixelFromCoordinate 为空

    在地图容器发生变化后 再调用其方法进行定位 暂时 报错setPosition的错误 跟踪源代码 发现是map getPixelFromCoordinate为null值 查找资料 发现是map还没有渲染完成 所以报错 解决办法 1 添加pos
  • Istio Pilot源码学习(三):xDS的异步分发

    本文基于Istio 1 18 0版本进行源码学习 5 xDS的异步分发 DiscoveryService主要包含下述逻辑 启动GRPC Server并接收来自Envoy端的连接请求 接收Envoy端的xDS请求 从ConfigControl
  • C#数组 一维、二维以及交错数组 C#学习杂记(五)

    1 一维数组基本概念 拥有连续的内存空间 存储一组相同类型的数据 数组长度不可更改 数组下标从0开始 2 数组基本使用 int arr 声明 arr new int 4 1 2 3 4 赋值并且初始化 string str str new
  • STM32F103ZET6【标准库函数开发】------05.通用定时器TIM4四个通道输出PWM信号

    STM32有四个通用定时器 现在介绍TIM4输出4路PWM的方法 TIM4可以选择不用重映射或者重映射 一 没有重映射 下面展示主要的time c main c函数的代码 include timer h void TIM4 PWM Init
  • linux LCD驱动实验

    文章目录 一 linux下LCD驱动解析 1 Framebuffer设备 2 LCD驱动解析 二 硬件原理图分析 三 LCD驱动程序编写 1 LCD 屏幕 IO 配置 2 LCD 屏幕参数节点信息修改 3 LCD 屏幕背光节点信息 四 运行
  • 深度学习(一)

    目录 1 深度学习要解决的问题 2 深度学习应用领域 3 计算机视觉任务 4 视觉任务中遇到的问题 1 深度学习要解决的问题 机器学习流程 数据获取 特征工程 建立模型 评估与应用 深度学习跟人工智能更贴切 机器学习中的一部分 特征工程的作
  • Arduino MQTT客户端库PubSubClient快速入门

    文章目录 目的 基础说明 示例代码 总结 目的 MQTT是比较常用在物联网设备中的通讯协议 这篇文章将使用 Arudino ESP32 作为MQTT客户端进行通讯使用演示 目前Arduino的MQTT客户端库中最常使用的是 PubSubCl
  • Swin Transformer:层次化视觉Transformer

    目录 论文下载地址 代码下载地址 论文作者 模型讲解 背景介绍 模型解读 总体结构 Swin Transformer模块 非重叠窗口的自注意力 连续Swin Transformer块中的移位窗口分区 移位的高效批量计算 网络结构 结果分析
  • python 函数

    函数是组织好的 可重复使用的 用来实现单一 或相关联功能的代码段 函数能提高应用的模块性 和代码的重复利用率 Python提供了许多内置函数 比如print 我们也可以自己创建函数 这被叫做用户自定义函数 定义函数的语法 def 函数名 参
  • 201632位matlab下载_Matlab R2016a x32

    之前小编为大家提供了matlab 2016a x64的下载 本来以为新版本不再支持32位系统了 不过官方还是很负责任地推出了支持xp win7等系统的matlab 2016a 32位版 新版本新增了模拟机器人系统 原型机器人算法以及机器人学
  • matlab常用分布极大似然估计函数用法(含例子实现)

    本博文源于matlab概率论运用 学过 概率论 的同学都知道 极大似然估计一般用于区间估计的 而matlab已经将其封装好由我们自己调用 参数估计的MATLAB函数 函数 功能 mu sigma muci sigmaci normfit x
  • 使用ScrollView实现下拉刷新(一)

    转载自 http blog csdn net a6920502 article details 8759244 使用ListView来做下拉刷新有很多例子 而且封装的很好 ListView有 header 但是如果不使用ListView的下
  • 使用C++完成以栈为基础的简易计算器,并使用Qt5制作可视化界面

    使用C 完成以栈为基础的简易计算器 并使用Qt5制作可视化界面 一 计算器内部逻辑构造 在完成计算器时选用栈数据结构 自己编写的和标准模板库中的都可 要求支持加减乘除和逻辑与或非运算 可计算整数和小数 这一部分有很多思路都可以实现 我的写法