C++实现计算器

一.实验目的(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)

由于种种原因,源码不方便在这儿展示。
需要源码的可以私信我。

猜你喜欢

转载自blog.csdn.net/weixin_42529594/article/details/120455067
今日推荐