一、
相信许多学习c语言的初学者在做简易计算器的时候,都会毫无头绪,碰到许多问题,就拿我自己举例子,我首先想到这个思路。
1.将用户输入的运算式作为字符串,(以''\0'结尾)
2. 利用循环语句,将字符串作为字符数组。进行循环,并在每个运算符号处做标记。两个运算符号间的字符即为数字,将两个符号间的字符放在一起作为数字,而后提取出来,用字符存储.将最后提取出的数字和符号作为中缀表达式交给计算机计算
这里遇到两个问题
1 如何在这个过程中将每个符号都保存起来,和数字保存起来.
2 如何将数字与存储起来的运算符相匹配进行运算.
二、
以上两个问题可以运用栈的知识解决。那么什么是栈呢?
栈是一个先进后出的结构,类似于堆盘子,先放到地上的盘子最后被取走(默认只能取走一个盘子)
栈其实就是操作受限的线性表,只有一个口,每一次操作时,这个口可以当出口也可以当入口.
例如:水桶,注入水时,水桶的头当做入口,倒水时,水桶的头当做出口。
这里我用一个大佬博主的代码给大家讲解,他也是运用了栈的知识做了一个简易计算器,以下是他的博客链接:
用c语言实现一个简单的计算器(数据结构)_利用数据结构在c语言中实现计算器的仿真-CSDN博客 在他的代码基础上,我加上了自己理解的标解注释,能让读者更快理解运用栈的知识,同时运用gotoxy函数,做了一个计算器控制台的界面,让画面更加美观漂亮,同时 我又分成了头文件calculator.h, 函数源文件calculator,c以及test.c文件三个模块,让整个代码更加美观整洁。大家可以先试着理解这位大佬的代码后,再来看我的改进和注释版的代码。以下就是我改进后的代码。
calculator.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <stdlib.h>
#include <windows.h>
int Preemption(a, b);
void gotoxy(int x, int y);
calculator,c
#include "calculator.h"
void gotoxy(int x, int y) {
COORD coord = { x, y };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
void display_calculator() {
gotoxy(25, 1);
printf("__________________________________");
gotoxy(25, 2);
printf("| | | |");
gotoxy(25, 3);
printf("| 欢迎使用多功能计算器 |");
gotoxy(25, 4);
printf("| |_____________________________| |");
gotoxy(25, 5);
printf("| | | |");
gotoxy(25, 6);
printf("| |_____________________________| |");
gotoxy(25, 7);
printf("| |");
gotoxy(25, 8);
printf("|___ ___ ___ ___ ___ ___ ___ ___ _|");
gotoxy(25, 9);
printf("| _____ _____ _____ _____ |");
gotoxy(25, 10);
printf("|| ⑨ | | ⑧ | | ⑦ | | * | |");
gotoxy(25, 11);
printf("| _____ _____ _____ _____ |");
gotoxy(25, 12);
printf("| _____ _____ _____ _____ |");
gotoxy(25, 13);
printf("|| ⑥ | | ⑤ | | ④ | | - | |");
gotoxy(25, 14);
printf("| _____ _____ _____ _____ |");
gotoxy(25, 15);
printf("| _____ _____ _____ _____ |");
gotoxy(25, 16);
printf("|| ③ | | ② | | ① | | + | |");
gotoxy(25, 17);
printf("| _____ _____ _____ _____ |");
gotoxy(25, 18);
printf("| _____ _____ _____ _____ |");
gotoxy(25, 19);
printf("|| | | = | | | | / | |");
gotoxy(25, 20);
printf("| _____ _____ _____ _____ |");
gotoxy(25, 21);
printf("__________________________________");
}
void display_ending() {
printf("#######################\n");
printf("# #\n");
printf("# 谢谢使用 #\n");
printf("# #\n");
printf("########################\n");
}
int Preemption(char a, char b) //符号优先级比较,a为当前读入,b为栈顶元素
{
int c; //c反馈指令信息 0.结束 1.弹出 2.进栈 3.删除当前元素及栈顶元素 4.报错
switch (a)
{ //c=2的情况,也就是读入符号直接入栈,意味着读入符号(新运算符)优先级>栈顶元素符号
case '#':if (b == '#') c = 0; //c=1的情况,也就是读入符号优先级<栈顶元素符号,栈顶符号出栈计算后,读入符号(新运算符)才能入栈
else c = 1;break;
case '+':if (b == '#' || b == '(') c = 2;
else c = 1;break; //加法减法规则一样
case '-':if (b == '#' || b == '(') c = 2;
else c = 1;break;
case '*':if (b == '*' || b == '/') c = 1;
else c = 2;break; //乘法除法规则一样
case '/':if (b == '*' || b == '/') c = 1;
else c = 2;break;
case '(':c = 2;break; //因为先算括号里的,读取到'('时直接c=2入栈
case ')':if (b == '(') c = 3; //读取到')'时,')'比+ - * / 优先级都小,c=1对括号里式子进行计算,除非遇到'('
else c = 1;break;
default:c = 4;break;
}
return c;
}
test.c
#include "calculator.h"
int main()
{
display_calculator();
gotoxy(27, 7);
char str[50] = { '\0' };
char* p = str;
double* p3, * p4, a = 0, b = 0;
char* p1, * p2;
char stack1[20]; //符号栈 栈顶指针p2,栈底指针p1
double stack2[20]; //数字栈 栈顶指针p4,栈底指针p3
p1 = p2 = stack1;
p3 = p4 = stack2;
*p2++ = '#'; //'#'先入符号栈
printf("请输入:");
gets(str);
strcat(str, "#"); //将'#'拼接到表达式末尾
while (*p != '\0')
{
if (*p <= '9' && *p >= '0')
{
a = a * 10 + (*p - '0'); //数字字符转数字
if (*(p + 1) > '9' || *(p + 1) < '0') //如果数字字符后一位为符号字符,则前面数字进数栈
{
*p4++ = a; //a入栈
a = 0;
}
p++;
}
else //读到字符时
{
switch (Preemption(*p, *(p2 - 1))) //*(p2-1)为符号栈顶元素,*p和*(p2-1)都随while循环一直变化
{
case 0:
gotoxy(1, 23);
printf("计算结果为:%.2lf\n", *p3);//直接输出数字
p++;
break;
case 1:
b = *--p4; //b=a读取的数字出栈即将进行运算,因为上方p4++入栈过了
switch (*(--p2))//判断栈顶元素出栈的符号,使用过的栈顶符号也会忽略删除,栈顶元素也变了
{
case '+':*(p4 - 1) = *(p4 - 1) + b;break;//得到的结果还要入操作数栈,这里相当于栈顶元素变为了两者相加结果,所以不用对最底下那个数(现在变成了数栈顶元素)进行出栈操作了
case '*':*(p4 - 1) = *(p4 - 1) * b;break;
case '-':*(p4 - 1) = *(p4 - 1) - b;break;
case '/':*(p4 - 1) = *(p4 - 1) / b;break;
}
break;
case 2:
*p2++ = *p++;//将*p入字符栈,(先赋值再++)继续读取下一个数字字符,数字字符变成数字后还会读下一个字符
break;
case 3: //'('与')'相遇情况
p++;
p2--; //指针下移,栈顶元素不是‘(’,往下移一位变为'('下的那个符号
break;
case 4:
gotoxy(1, 24);
printf("程序读到了无法计算的符号,出错了\n");
p++;
break;
}
}
}
gotoxy(1, 26);
display_ending();
return 0;
}
以下是运行的结果视频:
这里涉及的算法就是
创建数组作为栈的空间载体,建立判断符号优先级比较的函数,运用指针的移动,遍历表达式,数字符号入数字栈,读入符号优先级大于符号栈顶元素入符号栈,否则数字栈出栈两个元素,符号栈栈顶元素出栈完成一次运算进,运算结果,运算结果还是在数栈内,此时参与运算的原符号栈栈顶出栈会默认忽略该栈顶指针所指向位置的元素,所以我们记该操作为,删除栈顶元素,栈顶元素变为新的元素。再通过选择控制,循环控制,不断地入栈,出栈计算,最终结果将在数字栈底,此时符号栈底’#’与表达式末尾’#’相遇,视为运算结束,取数字栈底元素将最终计算结果打印出来,完成。
三、问题与改进
但细心的同志会发现,这里虽然能计算出很大的数,但能输入的运算式子并不是无限长的,由于运算式的数组长度是自己定义的,如果进行百千个操作数的运算,则会导致缓存溢出的问题,出现报错,此时我们想到了malloc函数实现动态内存分配,但查阅资料得知在使用malloc函数开辟的空间中,不能进行指针的移动,因为一旦移动之后可能出现申请的空间和释放空间大小的不匹配,这与我们用栈的方法相冲突,所以应该用创建柔性数组解决问题,柔性数组是C语言中的一种特殊数组,它的长度是可变的。在柔性数组中,可以通过指针的移动来访问数组中的元素。但由于时间有限,目前只用了栈的知识解决问题。
还有就是输入'/'后后面禁止输入0,0不能作为分母数字的输入,还有就是不能输入小数,如1.1,2.2,程序会将小数点作为符号判断,视为读取到无法计算的符号。这些问题大家可以自己去尝试解决。
四、总结
那么内容到这里差不多就结束啦,做一个小项目的过程需要学习这么多知识,我也是在边学边做中收获了许多c语言的知识点,希望读者们发现有可以改进或错误的地方能及时评论指正,有望大家多多包涵和理解,共勉!