课程设计报告
20##年 12月 31日
目 录
1.设计目的:........................................................ 1
2.设计要求:........................................................ 1
3.设计方案:........................................................ 1
4.设计内容:........................................................ 2
4.1.需求分析.................................................. 2
4.2.概要设计.................................................. 2
4.3.详细设计................................................... 4
4.4.调试分析与结果............................................. 7
4.5:使用说明................................................. 11
5.总结:.......................................................... 12
6.附录三:源代码.................................................. 12
表达式求值问题
1.设计目的:
《数据结构课程设计》是计算机专业集中实践性环节之一,是学习完《数据结构》课程后进行的一次全面的综合练习。其目的是:
(1)要达到理论与实际应用相结合,使学生能够根据数据对象的特性,学会数据组织的方法,能把现实世界中的实际问题在计算机内部表示出来,并培养良好的程序设计技能。
(2)在实践中认识为什么要学习数据结构,掌握数据结构、程序设计语言、程序设计技术之间的关系,是前面所学知识的综合和回顾。
2.设计要求:
以字符序列的形式从键盘输入语法正确的、不含变量的整数(或实数)表达式,实现对算术四则混合运算表达式的求值。当用户输入一个合法的算术表达式后,能够返回正确的结果。能够计算的运算符包括:加、减、乘、除、括号,对于异常表达式能给出错误提示。
3.设计方案:
任何一个表达式都是由操作符,运算符组成的。我们分别用顺序栈来寄存表达式的操作数和运算符。栈是限定于紧仅在表尾进行插入或删除操作的线性表。顺序栈的存储结构是利用一组连续的存储单元依次存放自栈底到栈顶的数据元素。
为了实现算符优先算法。可以使用两个栈。一个称为OPF,用以寄存运算符,另一个称做OPS,用以寄存操作数或运算结果。
1.首先置操作数栈为空栈,表达式起始符”#”为运算符栈的栈底元素;
2.依次读入表达式,若是操作符即进OPS栈,若是运算符则和OPF栈的栈顶运算符比较优先权后作相应的操作,直至整个表达式求值完毕(即OPF栈的栈顶元素和当前读入的字符均为”#”)。
4.设计内容:
4.1.需求分析
本程序所做的工作为:能直接求出四则表达式的值,并输出;本程序可用于小学教师对学生作业的快速批改以及对数值位数要求较大的科学运算。
4.2.概要设计
1.//用于存储操作数和运算结果(OPS):
ADT LinkStack{
数据元素:此链栈中的所有元素类型为字符型的数字字符
数据关系:栈中数据元素之间是线性关系。
基本操作:
(1)InitStack(LinkStack &head)
操作结果:构造一个空栈head
(2)IsEmpty(LinkStack head)
初始条件:栈head已存在
操作结果:若栈为空栈,则返回TRUE,否则FALSE
(3)Push(LinkStack &head,ElementType element)
初始条件:栈head已存在
操作结果:插入元素element为新的栈顶元素
(4)Pop(LinkStack &head,ElementType &element)
初始条件:栈head已存在且非空
操作结果:删除head的栈顶元素,并用e返回其值
(5)GetTop(LinkStack head, ElementType &element)
初始条件:栈head已存在并且非空
操作结果:用e返回head的栈顶元素
(6)DestroyStack(LinkStack &head)
初始条件:栈head已存在
操作结果:栈head 被销毁
}ADT LinkStack
2.//用于存储运算符(OPF):
ADT LinkCharStack{
数据对象D:元素类型为字符型的符号字符
数据关系R:
基本操作:栈中数据元素之间是线性关系。
(1)CInitStack(LinkCharStack &head)
操作结果:构造一个空栈head
(2)CIsEmpty(LinkCharStack head)
初始条件:栈head已存在
操作结果:若栈为空栈,则返回TRUE,否则FALSE
(3)CPush(LinkCharStack &head,ElementType element)
初始条件:栈head已存在
操作结果:插入元素element为新的栈顶元素
(4)CPop(LinkCharStack &head,ElementType &element)
初始条件:栈head已存在且非空
操作结果:删除head的栈顶元素,并用e返回其值
(5)CGetTop(LinkCharStack head, ElementType &element)
初始条件:栈head已存在并且非空
操作结果:用e返回head的栈顶元素
(6)CDestroyStack(LinkCharStack &head)
初始条件:栈head已存在
操作结果:栈head 被销毁
}ADT LinkCharStack
3.系统中子程序及功能要求:
Comop(char ch):判断输入的字符是否为运算符
char Precede(char ch,char c):比较两个运算符的优先级,ch是栈顶字符,c是表达式字符。
ElementType Operate(ElementType a,char ch,ElementType b):解析表达式中的双目运算,其返回的结果即为双目运算表达式的值。
int error(char *str) :错误提示函数,实现对多种非法四则表达式的判断,并给出提示,让用户更正自己的输入错误。
void MenuPrint():主菜单打印函数。
void Clear():清屏函数。
ElementType EvaluateExpression(char *exp):这是此程序的核心函数,可以综合其它子函数,实现最终的表达式求解。
各程序模块之间的调用关系(子程序编号见上):
主函数可调用子程序5,6,7。
子程序7可调用子程序1,2,3,4。
4.3.详细设计
此算法的基本思想:
首先置操作数栈OPS为空栈,表达式起始符“#”为运算符的栈底元素;依次读入表达式中每个字符,若是操作数则进栈,若是运算符则和OPF栈的栈顶运算符比较优先权作相应操作,直至整个表达式求值完毕(即OPF栈的栈顶元素和当前读入的字符均为“#”)
此算法的伪代码:
ElementType EvaluateExpression(char *exp)
{ 定义两个字符变量c和ch,c代表输入表达式中的字符,ch代表栈顶运算符;
定义字符指针 *p,*q,*temp;temp指向运算符后面的一个字符
double i=0,a=0,b=0;
将传入的实参赋给p,q;
定义一个运算符栈 OPF;
定义一个操作数栈 OPS;
调用函数InitStack()初始化栈OPS;
调用函数CInitCharStack()初始化栈OPF;
调用函数CPush(OPF,'#')将#压入运算符栈;
c=*p;temp=p;p++;
if(第一个字符就为‘-’)
{
c=*p;temp=p;p++;
}
while(栈不为空或表达式没有结束)
{//进入最外层循环
if(不是运算符)//则解析数字字符串然后进操作数栈
{
整数部分m=0;
小数部分n=0;
while(没有遇到小数点并且为数字字符)
{ 解析整数部分m }
if(遇到小数点)
{ 解析小数部分
c=*p;
将p指针移到第一个出现的字符;
将q指针指向小数的最后一位;
while(p指针不指向’.’)
{
将p指向的字符转为小数n
p--;
}
p=q;
p++;
}
if(运算符为‘-’并且运算符前一个为‘(’或者为表达式的开始)
调用Push(OPS,-(m+n))将m+n的相反数入栈;
else
调用Push(OPS,m+n)将m+n入栈;
}数字进栈结束
else//是运算符时则进栈OPF
{ if(运算符为‘-’&&运算符前一个为‘(’)
{ c=*p;
temp=p;
p++;
}
else
{ 调用函数CGetTop(OPF,ch)得到OPF的栈顶元素;
switch(调用函数Precede(ch,c)判断栈顶元素与接收的字符的优生级别)
{
case 栈顶运算符优先权低:
调用函数CPush(OPF,c)将c入运算符栈;
接收下一个字符;
case 栈顶运算符优先权高:
运算符出栈得到ch;
数字栈连续出栈两次得到a,b ;
调用Operate(a,ch,b)并将结果入栈到数字栈;break;
case 优生权相等:脱括号并接收下一个字符;
调用CPop(OPF,ch)脱括号;接收下一个字符;
default:接收下一个字符;
}退出switch循环
}//else1
}//else2
}//退出最外层while循环
调用函数GetTop(OPS,i)得到栈顶元素i;
将两个栈消毁;
}EvaluateExpression函数结束
利用该算法对算术表达式3*(7-2)求值操作过程如下:
表一
4.4.调试分析与结果
附录一:测试数据
表二
按照附录中的测试数据,得出如下测试、分析结果:
当我们输入表格中两个正确的四则表达式时程序能准确地求得其值:
完全支持浮点数,正负数的运算;
而当我们输入第五组错误的表达式时,程序能做出正确判断,提醒用户输入的表达式错误。
其数据测试的情况见截图:
表达式一运算结果
表达式二运行结果
表达式三运行结果
表达式四运行结果
由表二可知以上四组表达式运行结果皆正确。
表达式五运行结果
由表二可知第五组表达式错误。
附录二:部分错误表达式
表三
4.5:使用说明
运行程序,首先出现主菜单。主菜单包括三个选项:选项a:表达式求解,选择该项可进行四则表达式的求解;选项b:清屏;选项c:退出程序,选择该项将退出程序。
5.总结:
这次课程设计让我更加了解了学到的C语言和数据结构。课程设计不仅要求对课本知识有较深刻的了解,同时要求程序设计者有较强的思维和动手能力同时这也使我更加了解编程思想和编程技巧。
这次课程设计让我有一个深刻的体会,那就是细节决定成败,编程最需要的是严谨,如何的严谨都不过分,往往检查了半天发现错误发生在某个括号,分号,引号,或者数据类型上。就像我在写EvaluateExpression()函数时,忘了指针的地址符值不用加*,这一点小小的错误也耽误了我几十分钟,所以说细节很重要。
程序设计时,也不要怕遇到错误,在实际操作过程中犯的一些错误还会有意外的收获,感觉课程设计很有意思。在具体操作中这学期所学的数据结构的理论知识得到巩固,达到课程设计的基本目的,也发现自己的不足之出,在以后的上机中应更加注意。
6.附录三:源代码
//这个栈是用来存储数字字符的
#include<stdlib.h>
# include <stdio.h>
# include <string.h>
#define ERROR 0
#define OK 1
#define TRUE 1
#define FALSE 0
typedef char ElemType;
typedef int Status;
typedef double ElementType;
typedef int Status;
# define STACK_INIT_SIZE 30
# define STACKINCREAMENT 10
# define NUMBER 30
typedef struct node{
ElementType data;
struct node *next;
}StackNode, *LinkStack;
void InitStack(LinkStack &head){
head=(LinkStack)malloc(sizeof(StackNode));
head->next=NULL;
}
Status IsEmpty(LinkStack head){
if(head->next==NULL)return TRUE;
else return ERROR;
}
Status Push(LinkStack &head,ElementType element){//入栈
LinkStack p;
p=(LinkStack)malloc(sizeof(StackNode));
if(p== NULL) return FALSE;
p->data=element;
p->next=head->next;
head->next=p;
return OK;
}
Status Pop(LinkStack &head,ElementType &element){//出栈
if(IsEmpty(head))return FALSE;
LinkStack temp=head->next;
element=temp->data;
head->next=temp->next;
free(temp);
return OK;
}
Status GetTop(LinkStack head, ElementType &element){
if(head->next==NULL)
return ERROR;
element =head->next->data;
return OK;
}
Status DestroyStack(LinkStack &head)
{
LinkStack q;
while(head)
{
q=head->next;
free(head);
head=q;
}
return TRUE;
}
//这个栈是用来存储符号字符的
typedef struct node1{
ElemType data;
struct node1 *next;
}StackCharNode,*LinkCharStack;
void CInitCharStack(LinkCharStack &head){
head=(LinkCharStack)malloc(sizeof(StackCharNode));
head->next=NULL;
}
Status CIsEmpty(LinkCharStack head){
return (head->next==NULL)?TRUE:FALSE;
}
Status CPush(LinkCharStack &head,ElemType element){
LinkCharStack temp=(LinkCharStack)malloc(sizeof(StackCharNode));
if(!temp)return ERROR;
temp->data=element;
temp->next=head->next;
head->next=temp;
return TRUE;
}
Status CPop(LinkCharStack &head,ElemType &element){
if(CIsEmpty(head))return FALSE;
StackCharNode *temp=head->next;
element=temp->data;
head->next=temp->next;
free(temp);
return TRUE;
}
Status CGetTop(LinkCharStack head,ElemType &element){
if(head->next!=NULL)
{
element=head->next->data;
return TRUE;
}
element='#';
return FALSE;
}
Status CDestroyStack(LinkCharStack &head){
LinkCharStack q;
while(head)
{
q=head->next;
free(head);
head=q;
}
return TRUE;
}
//判断ch是否为运算符
int Comop(char ch)
{
switch(ch)
{
case '+':
case '-':
case '*':
case '/':
case '(':
case ')':
case '[':
case ']':
case '{':
case '}':
case '#':return 1;
default:return 0;
}
}//Comop
//比较两个运算符的优先级
char Precede(char ch,char c)//ch是栈顶字符,c 是表达式字符
{
switch(ch)
{
case '+':
case '-':
if(c=='+'||c=='-'||c==')'||c=='#'||c==')'||c==']'||c=='}')return '>';
if(c=='*'||c=='/'||c=='('||c=='['||c=='{')return '<';
case '*':
case '/':
if(c=='+'||c=='-'||c=='*'||c=='/'||c==')'||c==']'||c=='}'||c=='#')return '>';
if(c=='('||c=='['||c=='{')return '<';
case '(':
if(c=='+'||c=='-'||c=='*'||c=='/')return '<';
if(c==')')return '=';
case ')':
if(c=='+'||c=='-'||c=='*'||c=='/'||c==']')return '>';
if(c=='#')return '>';
case'[':
if(c=='+'||c=='-'||c=='*'||c=='/'||c=='(')return '<';
if(c==']')return '=';
case']':
if(c=='+'||c=='-'||c=='*'||c=='/'||c=='}')return '>';
if(c=='#')return '>';
case'{':
if(c=='+'||c=='-'||c=='*'||c=='/'||c=='('||c=='[')return '<';
if(c=='}')return '=';
case'}':
if(c=='+'||c=='-'||c=='*'||c=='/')return '>';
if(c=='#')return '>';
case '#':
if(c=='+'||c=='-'||c=='*'||c=='/'||c=='('||c=='['||c=='{')return '<';
if(c=='#')return '=';
default:
return '$';
}
}//precede
//运算函数
ElementType Operate(ElementType a,char ch,ElementType b)
{
switch(ch)
{
case '+':return a+b;
case '-':return a-b;
case '*':return a*b;
case '/':
if(b==0)
{
return -32767;
}
return a/b;
default:
return -32767;
}
}//Operate
//错误提示函数
int error(char *str)
{int i=0;
while(str[i]!='#') //主要通过判断所有输入的字符数组str[30]
{
if( (
(str[i]=='+'||str[i]=='-'||str[i]=='*'||str[i]=='/'||str[i]=='.')
&& //其它函数只要一声明err=1也就说明输入有误
(str[i+1]==')'||str[i+1]==']'||str[i+1]=='}')
)
||(
(str[i]=='+'||str[i]=='*'||str[i]=='/'||str[i]=='.')
&&
(str[i-1]=='('||str[i-1]=='['||str[i-1]=='{')
)
||(
(str[i]==')' || str[i]==']'||str[i]=='}')&& str[i+1]=='.'
)
||(
str[i]=='.' &&( str[i+1]=='('||str[i+1]=='['||str[i+1]=='{')
)
||(str[i]==')' && str[i+1]=='(')
||(str[i]=='(' && str[i+1]==')')
||(str[i]=='[' && str[i+1]==']')
||(str[i]==']' && str[i+1]=='[')
||(str[i]=='{' && str[i+1]=='}')
||(str[i]=='}' && str[i+1]=='{')
||(
(str[i]==')'||str[i]==']'||str[i]=='}') && str[i+1]>='0'&&str[i+1]<='9'
)
||(
(str[i]>='0'&&str[i]<='9'&& (str[i+1]=='('||str[i+1]=='['||str[i+1]=='{')
)
||(str[0]=='+'||str[0]=='*'||str[0]=='/'||str[0]==')'||str[0]==']'||str[0]=='}')
||(
(str[i]=='+'||str[i]=='-'||str[i]=='*'||str[i]=='/'||str[i]=='.')
&&
(str[i+1]=='+'||str[i+1]=='-'||str[i+1]=='*'||str[i+1]=='/'||str[i+1]=='.')
)
||(int(str[i])>57&&(int(str[i])!=91 && int(str[i])!=93 && int(str[i])!=123 && int(str[i])!=125))
||(str[i]=='/' && str[i+1]=='0')
||(int(str[i])>31 && int(str[i])<38)
)
)
return 1;
else if(str[i]=='#')return 0;
i++;
}//while
return 0;
}//错误提示函数
//表达式计算函数
ElementType EvaluateExpression(char *exp){
char c,ch; //c代表输入表达式中的字符,ch代表栈顶运算符
char *p,*q,*temp;//temp指向运算符后面的一个字符
double i=0,a=0,b=0;
p=q=exp;
LinkCharStack OPF;//运算符栈
LinkStack OPS;//操作数栈
CInitCharStack(OPF);CPush(OPF,'#');
InitStack(OPS);
c=*p;temp=p;p++;
if(c=='-')
{
c=*p;temp=p;p++;
}
while(c!='#'||!CIsEmpty(OPF)) //栈不为空或表达式没有结束
{//*********************进入最外层循环*********************
if(!Comop(c))//不是运算符则解析数字字符串然后进操作数栈
{
double m=0,n=0;
while(c!='.'&&c>='0'&&c<='9')
{//解析整数部分
m=m*10+(c-48);
c=*p;
p++;
}
if(c=='.')
{//解析小数部分
c=*p;
while(c>='0'&&c<='9')
{p++;c=*p;}
q=p;
p--;
while(*p!='.')
{
n=n/10+(*p-48)/10.0;
p--;
}
p=q;
p++;
}
if(*(temp-2)=='('&&*(temp-1)=='-'||temp-1==exp)
Push(OPS,-(m+n));
else
Push(OPS,m+n);
}//*****数字进栈结束******
else//是运算符时则进栈OPF
{
if(c=='-'&&*(p-2)=='(')
{
c=*p;
temp=p;
p++;
}
else//else1
{
CGetTop(OPF,ch);
switch(Precede(ch,c))
{
case '<'://栈顶运算符优先权低
CPush(OPF,c);c=*p;temp=p;p++;break;
case '>'://栈顶运算符优先权高
CPop(OPF,ch);
Pop(OPS,b);Pop(OPS,a);
Push(OPS,Operate(a,ch,b));break;
case '='://脱括号并接收下一个字符
CPop(OPF,ch);c=*p;temp=p;p++;break;
default:
c=*p;temp=p;p++;
}//switch
}//else1
}//else2
}//退出最外层循环
GetTop(OPS,i);
DestroyStack(OPS);
CDestroyStack(OPF);
return i;
}//EvaluateExpression函数结束
//菜单函数
void MenuPrint()
{
printf("\t\t┌─────────┐\n");
printf("\t\t│多功能计算器 │\n");
printf("\t\t├(a)表达式求解│\n");
printf("\t\t├(b)清屏 │\n");
printf("\t\t├(c)退出 │\n");
printf("\t\t└─────────┘\n");
} //菜单函数
//清屏函数
void Clear()
{ system("cls");
MenuPrint();
}//清屏函数
//main主函数
void main()
{
double result=0;
char exp[NUMBER],c;
freopen("DS1.in", "r", stdin);
freopen("DS1.out", "w", stdout);
Clear();
printf("\t\t请输入你要进行的操作:\n");
while(1)
{
scanf("%c",&c);
switch(c)
{ case 'a':
Clear();
sr:
printf("输入要计算的表达式,以##结束\n");
scanf("%s",exp);
if(!error(exp))
{ result=EvaluateExpression(exp);
printf("计算结果为:%lf\n",result);
printf("请根据屏幕提示选择要进行的操作:\n");
scanf("%d",&c);
break;
}
else if(error(exp))
{printf("您输入的表达式有误!");
goto sr;
}
case 'b':Clear();
printf("\t\t请输入你要进行的操作:");
scanf("%d",&c);break;
break;
case 'c':system("cls");exit(1);
default:printf("输入有误!");
}//switch
}//while
}//main