解析中缀表达式计算器。
对于本计算器,存储的每个运算符或者操作数均是用string结构。
首先读取函数input接受一个中缀表达式(中缀表达式中将忽略空格的存在)例如:
2 + -3(其中加号左右的空格将是要被忽略掉的);然后将其存储在string数组中。
存储好后由一个函数将其转换为后缀表达式并将其存储在另一个string数组中。
例如: 1 + 2;则分配3个string 数组==〉string[1]=”1”;string[2]=”+”;string[3]=”2”;
在类 calculate中还提供了一个out_print函数该函数输出后缀表达式的内容并把他设置为public属性(为检查中缀表达式的是否成功转换为后缀表达式)。
要成功转换表达式有一下三点困难。
1. 对于中缀表达式转换为后缀表达式都是基于双目运算符的表示,对于单目运算符就不好使了
2 . log或者sum的运算符又该如何呢?
3. 负号和合减号的混用问题
这里提供了一个比较有意思的思想(当然好坏由各位看官来裁定了,我李惟其不敢妄自吹嘘的)
单目函数的转换 例子 ( sin(30) 、 2! )
就是将单目运算符也转为双目运算符来处理。也就是在前面添加了一个废弃的1而已了。
但是值得思考的是单目运算符分为两种,前缀和后缀。前缀如sin,后缀如! 阶乘。所以这个1还真能随便乱加了,你的考虑如何将其加入合适的位置。前缀应该加在前面,后缀应该加在后面。在处理完后完全可以再计算两个运算的函数中丢弃这个1了。
Lon sum 函数的转换 例子(log(2,2),sum(1,3) )
解决了单目运算符后又该解决 log sum 的函数了
像这样的表达式 log(2,2), 在数学上称为 log以2为底,2的对数,结果为1;
这种表达式将转化为 2 log 2 是不是和 2+2是一种性质呢?
负号和负数混用的问题解决
(1)对与负号,当表达式头一个出现 ‘-’ 时 如 -5 + 10 这里的 ‘-’将被认为是负号
(2)在前面出现’+’ 、‘-’号是认为是负号 如 1- -5或者1+ -5 这里5前面的 ‘-’被认为是负号
(3)将负号和数字用括号括起来时 如 1* (-5) 这里的 ‘-’被认为是负号
(4)其他情况下均认为是减号
OK,解决了这几个问题之后接下来就给点例子来说明一下转换后的结构。
中缀表达式 |
后缀表达式 |
结果 |
1+2 |
1 2 + |
3 |
1+2 * 3 |
1 2 3*+ |
7 |
1+sin(30)*30 ==>1+(1)sin(30)*30 |
1 1 30 sin 30 * + |
16 |
1+sum(1,3) ==> 1 1 sum 3 + (sum(1,3)相当于1+2+3) |
1 1 3 sum + |
7 |
1+log(2,2) |
1 2 log 2 + |
2 |
1+-5 |
1 (-5) + |
-4 |
1*(-5) |
1 (-5) * |
-5 |
函数名称转换为特定的字母
该计算器为了方便处理在一开始的时候就将一些不是单字符的函数进行改编,这个过程可以发生在中缀转换后或者转换前(在本计算器中是发生在转换前的)。
大概的含义如下:
(1)X-〉asin 反正弦函数
(2)Y-> acos 反余弦函数
(3)Z-> atan 反正切函数
(4)A-> sinh 双曲正弦函数
(5)B-> cosh 双曲正弦函数
(6)C-> tanh 双曲正切函数
(7)s-> sin 正弦函数
(8)c-> cos 余弦函数
(9)t-> tan 正切函数
(10)n-> ln 自然对数
(11)g-> lg 以10为底的对数
(12)q-> sqrt根号
(13)b-> abs 绝对值
(14)f-> (!)foct 阶乘函数
(15)G-> log 求对数
(16)m-> sum 求和函数
(17)p-> pow 求幂
改编函数名后的例子:
源式 |
改编后 |
1+sin 30 |
1 + s 30 |
cos 30 |
c 30 |
... |
... |
类的功能及分布如下图:
现在给出每个类的定义:
//AStringMemory.h head file
/***************************
李惟其
2012-1-29
学生 qq:2206374245
***************************/
#ifndef ASTRINGMEMORY_H
#define ASTRINGMEMORY_H
#pragma warning(disable:4267)
#pragma warning(disable:4183)
#include "definclude.h"
//存储字符串的类
struct AString
{
/*
m_pstr->中缀表达式 m_size->中缀表达式的大小
m_p_pstr->后缀表达式 m_psize->后缀表达式的大小
*/
int m_size,m_psize;
string *m_pstr,*m_p_pstr;
//接收的用户输入串
string m_str;
AString(){
m_pstr=NULL;m_size=0;
m_p_pstr=NULL;m_psize=0;
m_str="";
}
AString& operator = (const string &str){
//跳过空格
int n=str.size();
m_str.erase();
m_str.resize(n);
int j=0;
for( int i=0; i < n; i++ )
{
if( str[i] != ' ' )
m_str[j++]=str[i];
}
return *this;
}
~AString(){
//删除一个指向NULL的指针是安全的做法
delete [] m_pstr;
delete [] m_p_pstr;
}
//分配m_pstr内存
void Allocate_pstr(size_t size);
void Set_pstrSize(size_t num);
//分配m_p_pstr内存
void p_Allocate_pstr(size_t psize);
void p_Set_pstrSize(size_t num);
//返回m_str的大小
int GetStrSize();
//返回m_pstr的大小
int GetPstrSize() const;
//返回m__p_pstr的大小
int p_GetPstrSize() const;
};// ~ 中缀、后缀表达式及其大小
#endif
//MakeString.h head file
/***************************
李惟其
2012-1-29
学生 qq:2206374245
***************************/
#ifndef MAKESTRING_H
#define MAKESTRING_H
#include "AStringMemory.h"
class MakeString
{
public:
bool IsNum(char ch);
protected:
void Amend(AString &);
//这个函数用于检测前面的符号是否可以让后面的'-'解析为负号
bool IsSymb(char ch);
void StringToSString(AString& Astr);
//函数的改编字符定义
void FuncMean();
private:
//屏蔽拷贝构造函数
vector<string> Vstr;
string Sstr;
MakeString(const MakeString&);
public:
MakeString() {}
MakeString(AString&);
~MakeString() {}
};//~
#endif
//cal.h head file
/***************************
李惟其
2012-1-29
学生 qq:2206374245
***************************/
#ifndef CAL_H
#define CAL_H
#include "MakeString.h"
namespace liweiqi
{
class calculate
{
public:
//计算函数
long double eval(bool &b); //计算转换后的表达式
//输出函数
void print_out(string& str); //输出后缀表达式
//返回长度
int getlen(); //返回后缀表达式的长度
public://读取类函数
calculate(int wsys=0) {sys=wsys;} //默认的构造函数
calculate(const char* str,int wsys=0); //获取字符串
void input(const char *str,int wsys=0); //读取字符串
protected:
//判断、检查、比较类函数
bool isnum(const string &str); //判断是否为一串数字
bool is_prev_open(char ch); //判断是否为优先级运算符
bool isproc(char op1,char op2); //比较两个运算符的优先级
bool islegal(); //检查中缀表达式是否合法
int point_numAndch(const string &str); //检查小数点的数量和是否为数字
public:
bool isopen(char ch); //判断是否为运算符
protected:
//转换类函数
long double atox(const string &str); //将字符转换为要计算的类型
bool convert(); //将中缀表达式转换为后缀
private:
//操作类函数
int open_lev(char op1); //运算符等级
long double count(long double op1,long double op2,char open); //计算两个操作
private:
calculate(const calculate &) //屏蔽拷贝构造函数
{}
public:
//析构函数作必要的清理工作
~calculate() {}
void clearn();
private:
AString m_astr;
public:
static int sys; //0为角度值 1为弧度制
};//end calculate class declare
}
#endif
//MakeString.h head file
/***************************
李惟其
2012-1-29
学生 qq:2206374245
***************************/
#ifndef ERROR_H
#define ERROR_H
#include "definclude.h"
//错误信息类
namespace MYERROR
{
class any_error
{
public:
any_error();
//设置错误的信息构造函数
any_error(const string& info);
//拷贝构造函数
any_error(const any_error& obj);
public:
//获取错误的信息
any_error& operator = (const any_error& obj);
const char* what();
public:
virtual ~any_error() {}
private:
string error;
};//~ error info
}
#endif
到此基本的底层处理结束,接下来是界面的设计。
1.按钮控件
总共需要40个虚拟按键,我们就申明CButton[40] 40个按钮数组。
每个长和宽都是40,左右的间隔为10,上下的间隔为40。
2.单选按钮控件
单选按钮存在于所有按钮控件的上方,宽度为70(因为有两个汉字),高度为20
所有的控件均在在视图类CChildView类中申明。
下面是计算绘制40个按钮位置和两个单选按钮控件位置的代码:
申明如下:
3编辑框控件
编辑框控件在单选按钮控件的上面,高度为80,宽度是动态变化的,基本计算时是190,科学计算时是390
代码如下:
void CChildView::CalDrawButton()
{
//计算出每个按钮在view类中的坐标位置
//每个按键长和宽都为40,左右间隔为10,上下间隔为40
CRect Base;
Base.SetRect(0,130,40,170);//这是第一个按钮的坐标位置
m_ButRect[0][0].SetRect(0,130,0,130);//对按钮坐标的初始化
int i,j;
//给每个按键赋予输出位置
for( i=0;i<5;i++ )
{
for( j=0;j<8;j++ )
{
m_ButRect[i][j].SetRect(
Base.left+10,Base.top+10,Base.right+10,Base.bottom+10);
Base.left=m_ButRect[i][j].left+40;
Base.right=m_ButRect[i][j].right+40;
}
Base.SetRect(0,130+(i+1)*75,40,130+(i+1)*75+40);
}
//默认为正常显示法显示
this->m_PrintMeans=ios::dec;
//绘制两个单选按钮
m_SingleSelButtoon[0].Create("角度",WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,
CRect(10,100,80,120),this,ID_SINGLESELBUTTON);
m_SingleSelButtoon[0].SetCheck(BST_CHECKED);//将按钮设置为已选中
m_SingleSelButtoon[1].Create("弧度",WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,
CRect(80,100,150,120),this,ID_SINGLESELBUTTON+1);
}
编辑框的位置在视图类的构造函数中初始化,动态变化在框架类的菜单消息中调用视图类的接口函数设置
在CChildView类中
在CMainFrame类中
--MainFram.h
...
afx_msg void OnBaseMenu();//基本计算菜单按钮
afx_msg void OnSecMenu();//科学计算菜单按钮
...
--MainFram.cpp
...
ON_COMMAND(ID_Menu, &CMainFrame::OnBaseMenu)
ON_COMMAND(ID_32772, &CMainFrame::OnSecMenu)
...
//基本计算菜单
void CMainFrame::OnBaseMenu()
{
// TODO: 在此添加命令处理程序代码
if( !m_IsBase )
{
SetWindowPos(this,0,0,220,551,SWP_NOREDRAW|SWP_NOMOVE|SWP_NOZORDER);
this->m_wndView.SetEditPos(CRect(10,10,200,90)); //重画编辑框
DrawMenuBar();//重画菜单栏
m_IsBase=true;
}
}
//单击了科学计算菜单
void CMainFrame::OnSecMenu()
{
// TODO: 在此添加命令处理程序代码
if( m_IsBase )
{
SetWindowPos(this,0,0,430,551,SWP_NOREDRAW|SWP_NOMOVE|SWP_NOZORDER);
DrawMenuBar();//重画菜单栏
this->m_wndView.SetEditPos(CRect(10,10,400,90)); //重画编辑框
m_IsBase=false;
}
}
...
窗口大小的切换
最后的界面设计便是点击科学按钮后窗口要做相应的变化
这里使用的函数是 SetWindowPos,处理的消息就在上面的按钮消息中
功能回应
到这里界面的设计基本完成,接下来就是功能设计了。
在点击了相应的虚拟按键之后程序要做出相应的回应。
捕获按钮消息的声明为:
---ChildView.cpp
...
ON_COMMAND_RANGE(ID_BUTTON,ID_BUTTON+39,&CChildView::OnCalBaseButton)
...
这里将所有的按钮控件的ID连城一个等差数列,为的就是方便子程序处理。
要处理按钮控件消息分两步
1.在前期给按钮控件设置文本。
首先声明数组
string sstr[]=
{
"后退" , "清零" , "√" , "*" , "sin", "cos" , "tan" , "(",
"1","2","3","/","asin","acos","atan",")",
"4","5","6","+","sinh","conh","tanh",",",
"7","8","9","-","ln","lg","log","!",
"^",".","0","=","pow","abs","sum","π"
};
接下来成员函数将数组与控件数组的文本对应起来
//计算出40个按钮的位置
void CChildView::DrawBaseButton()
{
int id=0,i,j;
//控件具有的字体
for( i=0; i < 5; i++ )
for( j=0; j < 8; j++ )
{
this->m_CalButton[i][j].Create(_T(""),WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON
,this->m_ButRect[i][j],this,ID_BUTTON+id);
id++;
this->m_CalButton[i][j].SetFont(&m_UserFont);//设置用户字体
//就在这里
this->m_CalButton[i][j].SetWindowTextA(sstr[i*8+j].c_str());//给按钮添加文本
}
}
2.捕获消息
//40个按键的消息
void CChildView::OnCalBaseButton(UINT nID)
{
TheHitTest(nID);
}
处理函数:
//单击按钮后返回相应的字符串函数
void CChildView::TheHitTest(int key)
{
//是否发生错误的标志
static bool Errflag=false;
//如果没有发生错误,获取编辑框中的文本
if( !Errflag )
{
CString str;
m_UserEdit->GetWindowTextA(str);
m_EditString=str.GetBuffer();
m_StringNum=str.GetLength();
}
else
m_StringNum=0;
key-=ID_BUTTON;
if( key < 0 )
return;
//等号优先处理
if( key == 35 )
{
bool flags;
try
{
//申明一个计算器类并在构造函数中接受字符串
int wys;
if( BST_CHECKED == m_SingleSelButtoon[0].GetCheck() )
wys=0;
else wys=1;
//接受结果值,并获取计算是否成功的标志
m_calculate.clearn();
m_calculate.input(m_EditString.c_str(),wys);
long double value=m_calculate.eval(flags);
if( flags )//如果表达式计算成功
{
stringstream iostring;
//设置结果的显示方式
if( m_PrintMeans == ios::showpoint )//保留12位小数点的设置
iostring<<setiosflags(ios::fixed)<<setiosflags(ios::right)<<setprecision(12);
else if( m_PrintMeans == ios::dec )//让计算机选择的设置
iostring.setf(ios::dec);
else
iostring.setf(ios::scientific);//科学计数法的设置
iostring<<value;
iostring>>m_EditString;
}
else
{
m_EditString="表达式错误";
//在编辑框中显示错误信息,显示后清除
MYDEFINE(m_UserEdit,m_EditString);
Errflag=true;
return;
}
}catch(MYERROR::any_error err)
{
m_EditString=err.what();
MYDEFINE(m_UserEdit,m_EditString);
Errflag=true;
return;
}
catch(std::exception err)
{
m_EditString=err.what();
MYDEFINE(m_UserEdit,m_EditString);
Errflag=true;
return;
}
catch(...)
{
m_EditString="未知错误";
MYDEFINE(m_UserEdit,m_EditString);
Errflag=true;
return;
}
}
//后退的处理
else if( key == 0 )
{
if( m_StringNum > 0 )
m_EditString[--m_StringNum]='\0';
}
//清空的处理
else if( key == 1 )
{
m_StringNum=0;
m_EditString[m_StringNum]='\0';
}
//π的转换
else if( key == 39 )
{
m_EditString+="3.141592653589";
m_StringNum+=strlen("3.141592653589");
}
//普通的转换
else
{
m_EditString+=sstr[key];
m_StringNum+=sstr[key].size();
}
//设置编辑框中的文本
Errflag=false;
m_UserEdit->SetWindowTextA(m_EditString.c_str());
}
再次计算的基本功能完成
1.计算两日期之差。
没什么将的,创建一个对话框资源
如上图给控件添加处理程序就行了。具体看项目中的代码
2关机功能
解释同上。
3.调用控制台程序。
先增加一个菜单按钮消息,然后再框架窗口添加消息处理
调用程序的具体的代码如下:
int NewApp(char str[],int* ErrValue)
{
//这个参数需要对所有成员(第一个除外)清零,否则会造成垃圾数据
STARTUPINFOA si={sizeof(si)};
PROCESS_INFORMATION NewProcess;
BOOL err=CreateProcessA(NULL,str,NULL,NULL,FALSE,0,NULL,NULL,&si,&NewProcess);
*ErrValue=GetLastError();
CloseHandle(NewProcess.hProcess);
return err;
}