编辑数学公式绘制曲线

本文介绍了作者天一程开发的一款程序,该程序能够编辑数学公式并显示曲线。程序包括切分对话框、识别函数表达式和计算数学表达式值等部分。作者详细讲解了程序的实现过程,包括切分对话框的创建、函数表达式的识别和计算,以及程序中遇到的问题和解决方案。源码已上传至优快云供免费下载。

 

编辑公式显示曲线

 

为了实现编辑公式显示曲线的的程序,天一程花了一周时间从构思到编码,终于算是有了个雏形,如果有时间的花,天一程将会对此进行改进。希望各位围观大虾也指点指点,有什么改进的地方和建议,多提提,天一程感激不尽。下面就对本程序的内容详细的说明。

   

 

(文章中的图片由于原因显示不了,但天一程的相册里面有)

此程序的效果图如下:


程序效果

程序效果2

 程序效果3


 

补充几点:

1.               对于Expression适用于sin(x)、e^x、x^n、a*x+b等形式的复合运算,但是输入时必须以‘#’结束

2.               e^x的输入以E^x ,x^n的输入新式为x^n, a*x+b的输入形式为a*x+b

3.               对于指数运算的复合运算底数部分和指数部分只能是多项式,对于正弦函数的括号里面表达式也限于多项式

 

 

本程序主要分为三部分:1.切分对话框 2.识别函数表达式 3.计算数学表达式的值

类结构和文件结构如图所示:

类结构

文件结构

 


 

 

 

1.       切分对话框

 

主要的思想是建一个对话框资源和类,然后建一个框架类,将对话框类和框架类关联,然后按视图切分操作就可以了,视图切分学VC的朋友都比较熟悉了(天一程也是在网上找了一个切分对话框的例子,例子的下载地址为:http://www.codersource.net/mfc/mfc-advanced/mfc-splitter-window.aspx

 

添加对话框资源并添加静态文本控件,截图如下:

对话框

 

框架类的代码:

class CFlatFrameWnd : public CFrameWnd {                       //创建一个框架类

       DECLARE_DYNAMIC(CFlatFrameWnd);

    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

};

IMPLEMENT_DYNAMIC(CFlatFrameWnd, CFrameWnd);

 

BOOL CFlatFrameWnd::PreCreateWindow(CREATESTRUCT& cs)

{

       CFrameWnd::PreCreateWindow(cs);

       cs.dwExStyle &= ~WS_EX_CLIENTEDGE;

       return TRUE;

}

 

 

 

 

 

对话框类与框架关联以及创建视(切分的子窗口)的代码:

 

void CFormulaDrawDlg::CreateViews()

{

       CCreateContext ccc;

       ccc.m_pNewViewClass   = RUNTIME_CLASS(CRightView);//CLeftDlg

       ccc.m_pCurrentDoc     = NULL;

       ccc.m_pNewDocTemplate = NULL;

       ccc.m_pLastView       = NULL;

       ccc.m_pCurrentFrame   = NULL;

 

       // Because the CFRameWnd needs a window class, we will create a new one.

       CString strMyClass = AfxRegisterWndClass(CS_VREDRAW |

       CS_HREDRAW,

       ::LoadCursor(NULL, IDC_ARROW),

       (HBRUSH) ::GetStockObject(WHITE_BRUSH),

       ::LoadIcon(NULL, IDI_APPLICATION));

 

       CRect rect;

       GetDlgItem(IDC_VTEMPLATE)->GetWindowRect(rect);

       ScreenToClient(rect);

       // Create the frame window with "this" as the parent

       m_pFrame = new CFlatFrameWnd;

       m_pFrame->Create(strMyClass,"", WS_CHILD, rect, this);           //this指的是父窗口

       m_pFrame->ShowWindow(SW_SHOW);

 

 

       // and finally, create the splitter with the frame as

       // the parent

       m_cSplitter.CreateStatic(m_pFrame,1, 2);

       m_cSplitter.ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);

       m_cSplitter.CreateView(0,0, RUNTIME_CLASS(CLeftDlg ),CSize(250,100), &ccc);

       m_cSplitter.CreateView(0,1, RUNTIME_CLASS(CRightView),CSize(100,100), &ccc);

 

       CLeftDlg * pView = (CLeftDlg *)m_cSplitter.GetPane(0,0);

       ASSERT_VALID(pView);

       pView->setWnd(&m_listMain);

       pView->setOwner(this);

//     m_listMain.Create(LBS_NOTIFY|WS_CHILD|WS_VISIBLE|WS_VSCROLL,CRect(0,0,1,1),pView,IDC_MAIN);

 

/**/CRightView* pView2 = (CRightView*)m_cSplitter.GetPane(0,1);

       ASSERT_VALID(pView2);

       pView->setWnd(&m_listSlave);

       pView2->setOwner(this);

//     m_listSlave.Create(LBS_NOTIFY|WS_CHILD|WS_VISIBLE|WS_VSCROLL,CRect(0,0,1,1),pView,IDC_SLAVE);

      

       m_cSplitter.MoveWindow(0,0, rect.Width(), rect.Height());      

       m_cSplitter.ShowWindow(SW_SHOW);

 

}

 

切分对话框为两部分:左边为基于FormViewCLelfDlg右边为基于CView的视类CRightView

 

CleftDlg类:

CLeftDlg

 

其成员截图:

成员截图

 

从运行结果图可以看出分为四部分:

 

改变表达式:

void CLeftDlg::OnChangeExpression()

{

       // TODO: If this is a RICHEDIT control, the control will not

       // send this notification unless you override the CFormView::OnInitDialog()

       // function and call CRichEditCtrl().SetEventMask()

       // with the ENM_CHANGE flag ORed into the mask.

      

       // TODO: Add your control notification handler code here

       CRichEditCtrl *pRec=(CRichEditCtrl *)GetDlgItem(IDC_EXPRESSION);

       CString str="";

       int n=0;

       pRec->GetWindowText(str);

       char ch;

      

       n=str.GetLength();

             

       if(n>1)

       {

              ch=str.GetAt(n-1);

              if(ch=='#')

              {

                     str.SetAt(n-1,'/0');

                     m_strExpression=str.Mid(0,n-1);                      //注意长度会影响计算的结果,结果就是很纠结的错误即没有负数

                     n=str.GetLength();

                     (GetRightView()->plot)->SetExpression(m_strExpression);

                     GetRightView()->Invalidate(true);

              }

       }

}

 

设置颜色:

void CLeftDlg::OnSelchangeLinecolor()

{

       // TODO: Add your control notification handler code here

       CComboBox *pCcb=(CComboBox *)GetDlgItem(IDC_LINECOLOR);

    switch(pCcb->GetCurSel())

       {

       case 0:

              m_nLineColor=RGB(255,0,0);

              break;

       case 1:

              m_nLineColor=RGB(0,255,0);

        break;

       case 2:

              m_nLineColor=RGB(0,0,255);

       default:

              break;

       }

       (GetRightView()->plot)->SetLineColor(m_nLineColor);

       GetRightView()->Invalidate();             

}

 

设置线条风格:

void CLeftDlg::OnSelchangeLinestyle()

{

       CComboBox *pCcb=(CComboBox *)GetDlgItem(IDC_LINESTYLE);

    m_nLineStyle=pCcb->GetCurSel();

 

       (GetRightView()->plot)->SetLineStyle(m_nLineStyle);

       GetRightView()->Invalidate();

}

 

控制线条粗细:

void CLeftDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)

{

       // TODO: Add your message handler code here and/or call default

       CWnd *pWnd;

       if(pScrollBar->GetDlgCtrlID()==IDC_SPIN){

              CString strValue;

              m_fLineSize=(double)nPos/20.0;

              strValue.Format("%3.1f",m_fLineSize);

              pWnd=((CSpinButtonCtrl *)pScrollBar)->GetBuddy();

              pWnd->SetWindowText(strValue);

       }

      

       (GetRightView()->plot)->SetLineSize((int)m_fLineSize);

       GetRightView()->Invalidate();

       CFormView::OnVScroll(nSBCode, nPos, pScrollBar);

}

 

CRightView类:

CRightView

 

主要绘图代码在OnDraw函数里

void CRightView::OnDraw(CDC* pDC)

{

       CDocument* pDoc = GetDocument();

       // TODO: add draw code here

       CRect rect;

       GetClientRect(&rect);

       pDC->SetMapMode(MM_ANISOTROPIC);

  

    pDC->SetWindowExt(400,400);

       pDC->SetViewportExt(rect.right,-rect.bottom);

 

    pDC->SetViewportOrg(rect.right/2,rect.bottom/2);

//     plot->SetExpression("sin(x)");                       //2^x或者E^x会出问题(出现负数情况,强制转化的问题)

    pDC->MoveTo(-200,0);        //x

       pDC->LineTo(200,0);

 

       pDC->MoveTo(200,0);      

       pDC->LineTo(193,8);

 

       pDC->MoveTo(200,0);

       pDC->LineTo(193,-8);

 

       pDC->MoveTo(0,-200);       //y

       pDC->LineTo(0,200);

 

       pDC->MoveTo(0,200);

       pDC->LineTo(8,193);

 

       pDC->MoveTo(0,200);

       pDC->LineTo(-8,193);

 

       pDC->TextOut(10,20,"O");

 

       plot->ShowPlot(pDC);

 

//     pDC->Rectangle(&CRect(0,0,-50,-50));

}

 

 

左边对话框与右边视的通信主要有:

CRightView * CLeftDlg::GetRightView()

{

       CFormulaDrawDlg* pDlg=(CFormulaDrawDlg *)m_pDlgOwner;

       CSplitterWnd* p=&pDlg->m_cSplitter;

       CRightView *pView=(CRightView *)p->GetPane(0,1);

       return pView;

}

 

还有更新左边的内容时Invalidate()

 

以上是切分对话框部分

 

 

 

下面说说函数表达式识别部分和算术表达式求值部分:

 

主要思想是将函数表达式进行几次扫描和替换,第一次识别并替换sina*x+b)第二次识别并替换(a*x+b^(a*x+b) 第三次扫描替换a*x+b  (注:因为这还是初级版本,天一程并未将数学函数扩展完)

 

设计了一个类MathFVMathFormulaValue)来承载,对外提供GetExpression() GetFV(float x)  SetExpression(CString str)三个接口,成员截图如下:

成员接口

 

整个处理过程在GetFV(float x)函数,其他的函数都可以由其名表意,具体如下:

float MathFV::GetFV(float x)

{

       Records *rec=new Records[100];

       int sign[100],type[100];

    int n=0;

       char *temp_str="#";

       float result=0;

       CString str=strExpression;                     //strExpression不能重复使用,会造成错误,因为后面继续复用前面的表达式

 

       n=FindSin(str,rec);        //记录sin(x)的位置

 

       ReplaceSin(&str,rec,n,x);

 

       n=FindPow(str,rec,sign,type);       //记录x^x的位置

 

       ReplacePow(&str,rec,sign,type,n,x);

 

       n=FindX(str,rec);                   //记录x的位置

 

       ReplaceX(&str,rec,n,x);

 

       str.Insert(0,temp_str);             //将表达式首尾加上'#'

       str+=temp_str;

       result=EvaluateExpression(str);

 

       if(rec!=NULL)

       {

              delete []rec;

       }

 

       return result;

      

}

 

计算算术表达式的值用的是严蔚敏版的数据结构的栈的应用的那一节,(后来才发现用逆波兰式更为简单,而且通用,下次做此类的程序坚决用逆波兰式),废话少说,天一程继续。。。在此类中EvaluateExpression(CString str)专门计算算术表达式的值。其内容如下:

 

float MathFV::EvaluateExpression(CString str)

{

       Stack<char> *optr=new Stack<char>;               //运算符栈

       Stack<float> *optn=new Stack<float>;             //操作数栈

       float num,front,after;

       char ch,ch_top,*str_to_char;

       CString temp;

       unsigned int i=0,j=0,nextj=0,strlength=str.GetLength();

      

       optr->Push(str.GetAt(i++));   //'#'进栈

 

//     while((ch=strExpression.GetAt(i))!=NULL)

       while(i<strlength)

       {

        ch=str.GetAt(i);

              if(!IsOperator(ch))            //若为操作数

              {

                     j=1;

                     while(!IsOperator(str.GetAt(i+j)))

                            j++;                 //j个字符不是操作数

                     temp=str.Mid(i,j);

//                   str_to_char=&temp.GetAt(0);    //转化为字符指针

                     str_to_char=temp.GetBuffer(strlength);

            num=atof(str_to_char);   //字符转为数字

                     optn->Push(num);

                     nextj=j;                //记录i移动的位置

                     temp.ReleaseBuffer();

              }

             

              else                        //运算符

              {

                     if(ch=='(' && str.GetAt(i+1)=='-')                //负数的情况

                     {

                            j=2;

                            while(str.GetAt(i+j)!=')')

                                   j++;                 //j个字符不是操作数

                            temp=str.Mid(i+1,j-1);

                            str_to_char=temp.GetBuffer(strlength);

                            num=atof(str_to_char);   //字符转为数字

                            optn->Push(num);

                            nextj=j+1;                //记录i移动的位置

                            temp.ReleaseBuffer();

                     }

 

                     else

                     {

                            switch(Precede(ch_top=optr->GetTop(),ch))      

                            {

                            case '<':

                                   optr->Push(ch);

                                   break;

                                  

                            case '>':

                                   optr->Pop(ch_top);

                                   optn->Pop(after);                       //先进后出

                                   optn->Pop(front);

                                   optn->Push(Operate(front,after,ch_top));

                                   optr->Push(ch);

                                   if(ch==')')

                                   {

                                          optr->Pop(ch);

                                          optr->Pop(ch);

                                   }

                                   break;

                            case '=':

                                   optr->Pop(ch_top);

                                   break;

                            default:

                                   break;

                            }

                            nextj=1;

                     }

              }

             

              i=i+nextj;                           //记录下一次i的位置

       }

 

      

       while(!optr->Empty())       //栈不为空时

       {

              optr->Pop(ch);          

              switch(Precede(ch_top=optr->GetTop(),ch))      

              {

              case '>':

                     optr->Pop(ch_top);

                     optn->Pop(after);                       //先进后出

                     optn->Pop(front);

                     optn->Push(Operate(front,after,ch_top));

                     optr->Push(ch);

                     break;

              case '=':

                     optr->Pop(ch_top);

                     break;

              case '<':

                     optr->Push(ch);

                     break;

              default:

                     break;

              }

       }

       return optn->GetTop();

}

 

此函数用了辅助栈,这回算是知道栈的用处了,当初学数据结构的时候根本不知道怎么用,而且发现了模板类的好处了。

 

 

 

 

对于那个Plot类,主要是作为绘制对象而存在的。

 

总结和未解决的问题:

 

未解决的问题:

1.#’结束想改为回车

2. 运行程序的时候想让左边的控件里都显示默认的值,结果如下图所示:

程序运行结果

3.当显示E^x的时候出现问题,天一程已找出了,就是float形强制转化为int型的时候出问题,(如图,注意右边的与y轴平行的线)不知道怎么解决

指数函数

 

4.将函数表达式推广为通用的情况,即任何常见的数学表达式都适用

 

   总结:

   天一程花了一个星期的时间,这几天充满了辛酸和痛苦,但是面对一个个困难,最终还将一个个消灭而得以坚持下来,可算是体会到了独立实现自己想要的效果,还是很爽的。还是那句话,世上没有过不去的坎,只要你打算迈出这一步不坚持不回头,你就能走过去,除非你没有跨过去的勇气和决心。挑战自己,相信自己是最棒的,这才是真正的程序员的精神风貌。

 

   欢迎各位围观的大虾围观板砖,天一程感激不尽,源码天一程已打包上传到优快云,免费下载地址为http://download.youkuaiyun.com/source/3095250

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值