一.实验目的(Objects)
1.掌握栈的运算及应用;
2.了解算法的健壮性;
二.实验内容(Contents)
1.实现计算器类中 lp、rp和 operate函数;
2.完善计算器类 evaluate函数,增加对输入的合法性检查,包括滤掉所有非法输入及处理左 右括号不配对的输入;
3.编制应用程序测试这个计算器;
4.用如下表达式进行测试:
(56-23)/8-4# 期望结果:0.125
34+p(u89-12.3)k/3# 期望结果:59.5667
89.5*749+25)# 期望结果:输入有误
(8*(7-4)# 期望结果:输入有误
65*(72+98)(70-45)# 期望结果:输入有误
6*# 期望结果:输入有误
)5+3(# 期望结果:输入有误
5.验证计算结果。
三.实验步骤(Yourstepsorcodes)
1st.整体设计(Overalldesign)
本次实验整体设计是由两个类(class)作为主要框架。一个是 Stack类;一个是 Calculator类。其中, 对 Stack类使用了泛型(Generic)。 其整体设计如下:
/********************************************栈的构建*********************************************/
template<typenameT>
classStack{
//栈模板类的创建与其数据变量
private:
T* n;//栈中存储数据的数组;
Stac_Int Nlen;//栈当前长度;
Stac_Int capacity;//栈的最长长度;
Stac_Int top_;//存储当前栈顶元素的下标;
public:
Stack();//构造函数
~Stack();//析构函数
void push(Tx);//栈的 push函数,向栈顶添加元素
T top();//栈的 top函数,返回栈顶元素
bool isEmpty();//栈的为空判断
bool isFull();//栈的为满判断
Stac_Int backNlen();//返回栈的长度
void pop();//栈的 pop函数,弹出栈顶元素
void to_empty();//清空栈
};
//****************************************计算器类的构建*******************************************//
classCalculator{
private:
Stack<char> optr;//运算符栈;
Stack<double> opnd;//运算数栈;
char* at;//计算器类的运算式字符串的头指针;
int n;
int lp(chara);//返回字符栈头部字符的优先级命令;
int rp(chara);//返回录入读取字符的优先级命令;
double char_to_double(char*a,intn);
//该函数进行字符串的小数转换成 double类型的小数(但只对有一个点 的小数有作用)
double operate_double(chara,doublex1,doublex2);//该函数计算两数的四则运算
public:
Calculator();//无参构造函数;
Calculator(char*a,intnn);//含参构造函数;
~Calculator();//析构函数;
void operate();对输入的一串算术字符串式的处理(核心处理函数);
bool rr(chara)const;//辅助函数;
bool evaluate()const;//判断输入的一串算术字符串是否合法;
double result();//返回运算结果;
};
有关于 Stack类和 Calculator类的核心函数定义,我会在算法框架那一栏里详细说明。
2nd.算法框架(Algorithm Framework)
1. 关于 Stack类核心函数的算法框架:
核心函数为:
void push(T x);//栈的 push函数,向栈顶添加元素
T top();//栈的 top函数,返回栈顶元素
void pop();//栈的 pop函数,弹出栈顶元素
void to_empty();//清空栈
1>.关于 push函数:
//栈的 push函数,向栈顶添加元素
template<typename T>
void Stack<T>::push(T x){
if(!isFull()){
top_++;
n[top_]=x;
Nlen++;
}
else
printf("堆栈已满;创建失败!\n");
}
在栈不为空时,让保存栈顶元素脚码的 top_先自增一然后让 n[top_]=x;此时栈元素增加,Nlen++;在栈 为空时,输出“堆栈已满;创建失败!”。
2>.关于 pop函数:
//栈的 pop函数,弹出栈顶元素
template<typename T>
void Stack<T>::pop(){
top_--;
Nlen--;
}
只需要 top_–;Nlen–;即可。
3>.关于 top函数:
//栈的 top函数,返回栈顶元素
template<typename T>
T Stack<T>::top(){
returnn[top_];
}
直接返回顶端元素即可。
4>.关于 to_empty函数:
//清空栈
template<typename T>
void Stack<T>::to_empty(){
top_=0;
Nlen=0;
}
直接让栈顶端元素脚码为 0以及栈长度为 0即可。
2.关于 Calculator类核心函数的框架:
核心函数为:
void operate();对输入的一串算术字符串式的处理(核心处理函数);
double char_to_double(char*a,int n);
//该函数进行字符串的小数转换成 double类型的小数(但只对有一个点的小 数有作用)
以下为辅助函数:
int lp(char a);//返回字符栈头部字符的优先级命令;
int rp(char a);//返回录入读取字符的优先级命令;
double operate_double(char a,double x1,double x2);//该函数计算两数的四则运算
bool rr(char a)const;//辅助函数;
bool evaluate()const;//判断输入的一串算术字符串是否合法;
double result();//返回运算结果;
关于其主要定义,查看源代码。
1>.讨论 char_to_double函数的实现算法:
//该函数进行字符串的小数转换成 double类型的小数(但只对有一个点的小数有作用)
double Calculator::char_to_double(char*a,int n){
double su=0;
int i;
int t1=-1;
int t2;
for(i=0;i<n;i++){
if(a[i]=='.'){
t1=i; break;
}
}
if(t1!=-1){
t2=t1-1;
for(i=0;i<t1;i++,t2--){
su+=(a[i]-'0')*pow(10,t2);
}
int j=-1;
for(i=t1+1;i<n;i++,j--){
su+=(a[i]-'0')*pow(10,j);
}
}
else {
t2=n-1;
for(i=0;i<n;i++,t2--)
su+=(a[i]-'0')*pow(10,t2);
}
return su;
}
该函数中,su的 double类型变量是用来储存最终字符串类型的数字转换成 double类型之后的小数的。 首先传入字符串的数字,先进行判断,是否是带有小数点的:
for(i=0;i<n;i++){
if(a[i]=='.'){
t1=i;
break;
}
}
如果是,则使用 t1存储当前的数组元素的下标,t1初始为-1用来判断此字符串数是否是带有小数点的。 如果是则分为两部分处理:整数部分、小数部分。
if(t1!=-1){
t2=t1-1;
for(i=0;i<t1;i++,t2--){
su+=(a[i]-'0')*pow(10,t2);
}
int j=-1;
for(i=t1+1;i<n;i++,j--){
su+=(a[i]-'0')*pow(10,j);
}
}else {
t2=n-1;
for(i=0;i<n;i++,t2--)
su+=(a[i]-'0')*pow(10,t2);
}
如果不是,则只处理整数部分即可。最后返回 su。
算法复杂度分析: 最坏的情况是没有小数点,那么对每一个元素都要处理两次,因此是处理 2*n次。 也就是 O(n);
2>.关于 operate函数的实现;
//对输入的一串算术字符串式的处理
void Calculator::operate(){
int i;
int k=0;
int t=0;
char a1;
int ii;
double tt1,tt2;
optr.push('#');
for(i=0;i<n;i++){
if(at[i]>='0'&&at[i]<='9'){
/*这部分主要是在截取输入的算式字符串中的数字字符串部分, 截取之后需要借助 char_to_double()函数把字符串数字转换成 double类型的值,然后才可以压入 opnd栈中*/
k=i;
while((at[i]>='0'&&at[i]<='9')||at[i]=='.'){
t++;
i++;
}
char u[100];
for(ii=0;ii<t;ii++,k++)
u[ii]=at[k];
opnd.push(char_to_double(u,t));
t=0;
i--;
}
else if(at[i]=='#'&&optr.top()=='#') /*如果此时读取到'#',则什么都不做, 也意味着结束整体处理*/ else{
/*以下是算符优先级的实现*/
while(1){
if(rp(at[i])==-1) /*过滤非算符非数字的字符*/
break;
else if(rp(at[i])>lp(optr.top())){
optr.push(at[i]); /*如果传入字符优先级大于 optr栈顶字符优先级 则压入栈顶。*/
break;
}
else if(rp(at[i])<lp(optr.top())){
a1=optr.top();
optr.pop();
tt1=opnd.top();
opnd.pop();
tt2=opnd.top();
opnd.pop();
opnd.push(operate_double(a1,tt2,tt1)); /*如果传入字符优先级小于 optr栈顶字符优先级 则进行四则运算处理,处理结果,之后压入 opnd栈顶*/
}
else if(
rp(at[i])==lp(optr.top())){
optr.pop(); /*如果传入字符优先级与 optr栈顶优先级相等 则弹出 optr栈顶字符。*/
break; } } } } }
这一部分主要算法是字符优先级算法和截取字符串数字的算法: 如下:先确定算符优先级的算法思想:
可做如上图的算符优先级的想法规定。
首先横排运算符可称为 t2操作符,竖排的运算符可称为 t1操作符。上图显示了各个算符之间的优先 级的关系。
该优先级关系由四则运算法则导出:
-先乘除后加减
-先左后右
-先括号内后括号外
下面进行运算符操作顺序的算法思想说明:
*设立一个操作符栈 x1和一个操作数栈 x2。x1栈底放入结束符#。
*从左到右扫描表达式,若为操作数则压入 x2栈,继续扫描;若为操作符 t2,则将其与 x1栈顶元素 t1 比较:
-若 t2t1’#’,则运算结束,结果在 x2栈顶;
-若 t2的优先级高,则 t2入 x1栈,继续扫描;
-若优先级相等,则退出 x1栈顶元素‘(’,继续扫描;
-若 t2的优先级低,则从 x1栈退出 t1,从 x2栈退出两个操作数 b和 a,作运算 at1b后将结果压入 x2栈。
其实现如下:
if(at[i]=='#'&&optr.top()=='#') /*如果此时读取到'#',则什么都不做, 也意味着结束整体处理*/ ;
else{
/*以下是算符优先级的实现*/
while(1){
if(rp(at[i])==-1) /*过滤非算符非数字的字符*/
break;
else if(rp(at[i])>lp(optr.top())){
optr.push(at[i]); /*如果传入字符优先级大于 optr栈顶字符优先级 则压入栈顶。*/
break;
}
else if(rp(at[i])<lp(optr.top())){
a1=optr.top();
optr.pop();
tt1=opnd.top();
opnd.pop();
tt2=opnd.top();
opnd.pop();
opnd.push(operate_double(a1,tt2,tt1)); /*如果传入字符优先级小于 optr栈顶字符优先级 则进行四则运算处理,处理结果,之后压入 opnd栈顶*/
}
else if(rp(at[i])==lp(optr.top())){
optr.pop(); /*如果传入字符优先级与 optr栈顶优先级相等 则弹出 optr栈顶字符。*/
break;
}
}
然后是截取字符串数字的算法,并将其转换成 double类型的数压入 opnd栈顶:
if(at[i]>='0'&&at[i]<='9'){
/*这部分主要是在截取输入的算式字符串中的数字字符串部分, 截取之后需要借助 char_to_double()函数把字符串数字转换成 double类型的值,然后才可以压入 opnd栈中*/
k=i;
while((at[i]>='0'&&at[i]<='9')||at[i]=='.'){
t++;
i++;
}
char u[100];
for(ii=0;ii<t;ii++,k++)
u[ii]=at[k];
opnd.push(char_to_double(u,t));
t=0;
i--;
}
先使用 k存储最初是数字的那个字符在数组中的下标,然后进行继续遍历,直到某个 at[i]不是数字或 者’.’为止。使用 t计算该字符串的数字的长度。然后将其保存到 u[100]的一个新的数组中。最后调用 char_to_double()函数,把 u[t]转换成 double类型的可以直接进行四则运算的数字压入 opnd 栈顶。最后初始化 t。便于下次使用。
四.源程序(SourceProgram)
由于种种原因,源码不方便在这儿展示。
需要源码的可以私信我。