回 帖 发 新 帖 刷新版面

主题:[原创]混合四则运算的代码

混合四则运算的代码

  混合四则运算可以利用别人写好的控件,再就利用Excel(前提是安装了Excel)。我这
里说的,是自己编写代码。
  例如文本框有这样一个算式:3 * 7 - ((2 + 18)/5 + 6),如何编写代码?
  看起来是个很简单的式子,写起代码来却大费周章,耗费了我整整一天的时间。编程的
思路最后归纳为:
1.整个算式当作字符串来处理,只是在具体计算时化为数值,结果再转为字符串
2.不断地用计算结果替换原单项计算式,例如用“21”替换“3 * 7”

解题步骤如下:

预备工作:
1.去掉字符串中的空格,上式就变成了这样的字符串:“3*7+((2+18)/5+6)”
2.将小写字母转换为大写字母

开始计算:
1.利用函数 FindPlace 查找括号的位置,取出括号及其中的算式作为一个子字符串。如上
 式中的“(2+18)”。
2.将去掉括号的算式(如果没有括号就将整个算式)交给 analyze 函数,按照乘、除、加
 减的顺序进行计算。
3.在进行单项计算时,先确定运算符的位置,以这个位置为基础,分别获取前后两个数据和
 运算符本身,如上式中的“2”、“18”、“+”。这个步骤中最麻烦的是对“-”号的处
 理,因为它既可表示减法,也可以表示负数。
4.根据运算符计算(这是最轻松的步骤了)
5.每进行完一次计算,都要将结果转换成字符串,去替换由数据和运算符组成的子字符串。
 如“20/5+6”,除完后,用商去替换“20/5”这个子字符串,得到一个新的字符串:
 “4+6”
6.重复步骤1-5,上式就依次变成:“3*7+10”、“21-10”、“11”,结果在Text2显示。

  笔者根据以上叙述编写了代码。本代码还可以对2/8/16进制的数据进行四则运算(但计
算结果均为10进制),这三种进制的输入规则是:
1.在数据前加一个标识字母(大小写均可),B表示2进制,O表示 8进制,H表示16进制,除
 了这三个字母和16进制数字 A-F,计算式中不能出现其它字母
2.这三种进制的输入数据不能是小数,如:“100+H0.5”(16进制小数 0.5)是非法数据

下面就动手实验:
  新建一个窗体,添加两个文本框和一个按纽,其中Text1用于输入计算式,Text2用于显
示计算结果。点击按纽就开始计算

代码如下:

Option Explicit

Private Sub Command1_Click()
Dim st As String, MyData1 As String, MyData2 As String

st = Text1
If Len(st) < 3 Then Exit Sub
st = UCase(Replace(st, " ", "")) '去掉字符串中的空格并将字母转为大写

st = checkstring(st): If InStr(st, "错误") Then MsgBox st: Exit Sub '如果是非法数据,退出

Do While InStr(st, "H") > 0: st = AnyToDec(st, 16): Loop '将16进制转换为10进制
Do While InStr(st, "B") > 0: st = AnyToDec(st, 2): Loop '将2进制转换为10进制
Do While InStr(st, "O") > 0: st = AnyToDec(st, 8): Loop '将8进制转换为10进制

Do While InStr(st, "(") > 0 '如果有括号
  MyData1 = FindPlace(st)
  MyData2 = MyData1
  MyData1 = Mid$(MyData1, 2, Len(MyData1) - 2) '从取出的字符串中去掉左右括号
  MyData1 = analyze(MyData1)
  st = Replace(st, MyData2, MyData1) '用计算结果替代原字符串中的括号内容
Loop

Text2 = analyze(st)
End Sub

Function FindPlace(S As String) As String '查找括号的位置,取出括号及其中的算式
Dim i As Integer, k As Integer
i = 1
Do While i > 0
  i = InStr(i, S, "(")
  If i > 0 Then k = i: i = i + 1
Loop
If k > 0 Then i = InStr(k + 1, S, ")"): FindPlace = Mid$(S, k, i - k + 1)
End Function

Function analyze(nSt As String) As String
Dim i As Integer, z As String
Do While InStr(nSt, "*") > 0: nSt = operate(nSt, "*"): Loop '如果有*
Do While InStr(nSt, "/") > 0: nSt = operate(nSt, "/"): Loop '如果有/
Do While InStr(nSt, "+") > 0: nSt = operate(nSt, "+"): Loop '如果有+
Do While InStr(nSt, "-") > 0 '如果有-
  If Left(nSt, 1) = "-" Then
    For i = 2 To Len(nSt)
      z = Mid$(nSt, i, 1): If InStr("*/+-", z) > 0 Then Exit For
    Next
  End If
  If i > Len(nSt) Then Exit Do '如果 - 号后面没有其它运算符,那么这是一个负数
  nSt = operate(nSt, "-")
Loop
analyze = nSt
End Function

Function operate(S As String, sign As String) As String '完成一次运算
Dim k1 As Integer, k2 As Integer, S1 As String, S2 As String, z As String, n As String, i As Integer
i = InStr(2, S, sign) '获得运算符位置

z = Left$(S, i - 1) '获得运算符前的字符串
For k1 = Len(z) To 1 Step -1
  n = Mid$(z, k1, 1): If k1 = 1 And n = "-" Then n = "." '如果是负数而不是减号
  If InStr(".0123456789", n) = 0 Then Exit For
Next
k1 = k1 + 1
S1 = Mid$(z, k1) '获得运算符前的数据

z = Mid$(S, i + 1) '获得运算符后的字符串
For k2 = 1 To Len(z)
  n = Mid$(z, k2, 1): If k2 = 1 And n = "-" Then n = "." '如果是负数而不是减号
  If InStr(".0123456789", n) = 0 Then Exit For
Next
k2 = k2 - 1
S2 = Mid$(z, 1, k2) '获得运算符后的数据

z = Mid$(S, k1, k2 + i - k1 + 1) '获得一个完整的算式字符串用于替换
operate = Replace(S, z, Format(nCount(S1, S2, sign)))
End Function

Function nCount(Num1 As String, Num2 As String, symbo As String) As Double
Select Case symbo
  Case "+": nCount = Val(Num1) + Val(Num2)
  Case "-": nCount = Val(Num1) - Val(Num2)
  Case "*": nCount = Val(Num1) * Val(Num2)
  Case "/": If Num2 <> "0" Then nCount = Val(Num1) / Val(Num2) Else MsgBox "除数不能为0!计算结果是错误的"
End Select
End Function

Function AnyToDec(mSt As String, Num As Integer) As String '2/8/16进制转为10进制
On Error GoTo 100
Dim i As Integer, k As Integer, A As Integer, L As Integer, j As Integer, d As String, B As Double

'查找标识所在的位置
k = InStr(mSt, "H")
If k = 0 Then k = InStr(mSt, "B")
If k = 0 Then k = InStr(mSt, "O")

For i = k + 1 To Len(mSt) '获取数据
  d = Mid$(mSt, i, 1): If InStr("*/+-)", d) > 0 Then Exit For
Next
d = Mid$(mSt, k + 1, i - k - 1)

L = Len(d)
For j = 0 To L - 1 '开始转换
  A = Asc(Mid$(d, L - j, 1)) - 48
  If A < 0 Or A > 9 And A < 17 Or A > 22 Then A = 0
  If A > 9 Then A = A - 7
  If A > 0 Then B = B + A * Num ^ j
Next

AnyToDec = Replace(mSt, Mid$(mSt, k, i - k), Format(B)) '用10进制字符串替换原2/8/16进制字符串
100
End Function

Function checkstring(ByVal S As String) As String '判断计算式是否合法
Dim i As Integer, j As Integer, k As Integer, z As String, z1 As String, z2 As String
z = S
For i = 1 To Len(z)
  If InStr(".0123456789ABCDEFHOB()+-*/", Mid$(z, i, 1)) = 0 Then S = "错误!发现非法数据": GoTo endcheck
Next

z = S: i = 0
Do While InStr(z, "(") > 0: i = i + 1: z = Mid(z, InStr(z, "(") + 1): Loop '统计左括号数量
z = S: j = 0
Do While InStr(z, ")") > 0: j = j + 1: z = Mid(z, InStr(z, ")") + 1): Loop '统计右括号数量
If i <> j Then S = "错误!表达式中括号不成对": GoTo endcheck

z2 = "H": z1 = "": GoSub 100: If z1 <> "" Then S = z1: GoTo endcheck
z2 = "O": z1 = "": GoSub 100: If z1 <> "" Then S = z1: GoTo endcheck
z2 = "B": z1 = "": GoSub 100: If z1 <> "" Then S = z1

endcheck:
checkstring = S
Exit Function

100
j = 0
Do
  j = j + 1: j = InStr(j, S, z2): If j = 0 Then Exit Do '查找标识所在的位置
  For i = j + 1 To Len(S) '获取数据
    z = Mid$(S, i, 1): If InStr("*/+-)", z) Then Exit For
  Next
  z = Mid$(S, j + 1, i - j - 1): If InStr(z, ".") Then z1 = "错误!2/8/16进制数据不能是小数": Exit Do
  If z2 = "O" Then GoSub 200: If z1 <> "" Then Exit Do
  If z2 = "B" Then GoSub 300: If z1 <> "" Then Exit Do
  
Loop While j > 0
Return

200
For i = 1 To Len(z) '检查8进制数据
  If InStr("01234567", Mid$(z, i, 1)) = 0 Then z1 = "错误!发现非法八进制数据": Exit For
Next
Return

300
For i = 1 To Len(z) '检查2进制数据
  If InStr("01", Mid$(z, i, 1)) = 0 Then z1 = "错误!发现非法二进制数据": Exit For
Next
Return
End Function


  OK,现在随便输入几个算式看看:
输入“b1010-((o2+18)/h5-h6)-o3*o7”,计算得:-9
输入“1010-hff*((o7-18)/ha-h6)/3*o7”,计算得:1096.21428571429
输入“(hf*10*ha-b1010*50)/o31”,计算得:40
完全正确!


2007.6.16

回复列表 (共12个回复)

沙发

不错,支持!
但是计算  9*8-6/2+(12-10)*3
结果是63 而不是75?
如果输成 (9*8-6/2)+(12-10)*3=75了

期待修正........

板凳

是个不错的程序。
但是,代码过于复杂。
我记得使用‘从“表达式”开始逐层定义的方法,一直推到字符集合’的思路编写出的程序很简洁。

3 楼

感谢帅哥为我指出一个重大的BUG,现将其中的analyze函数修改如下:

Function analyze(nSt As String) As String
Dim i As Integer, k As Integer, z1 As String, z2 As String
k = 1
Do
  k = k + 1: z1 = Mid$(nSt, k, 1)
  If z1 = "*" Then nSt = operate(nSt, "*"): k = 1 '如果是乘法
  If z1 = "/" Then nSt = operate(nSt, "/"): k = 1 '如果是除法
Loop Until InStr(nSt, "*") = 0 And InStr(nSt, "/") = 0 Or k >= Len(nSt)

k = 0
Do
  k = k + 1: z1 = Mid$(nSt, k, 1)
  If z1 = "+" Then nSt = operate(nSt, "+"): k = 0 '如果是加法
  If z1 = "-" Then '如果是减法或负数
    If k > 1 Then
      nSt = operate(nSt, "-"): k = 0
    Else
      For i = 2 To Len(nSt)
        z2 = Mid$(nSt, i, 1): If InStr("*/+-", z2) > 0 Then Exit For
      Next
      If i > Len(nSt) Then Exit Do '如果 - 号后面没有其它运算符,那么这是一个负数
    End If
  End If
Loop Until InStr(nSt, "+") = 0 And InStr(nSt, "-") = 0 Or k >= Len(nSt)
  
analyze = nSt
End Function

老大的建议很好,我有空就试着按你说的思路再编写一次

4 楼

这些功能有现成的控件和API可以实现
不过LZ精神可佳,学习、赞偿!

5 楼

小弟请求LZ将程序的文件(完成的所有VB原始文件)发送到 wedown@163.com
小弟我是自学的,所以需要借鉴大虾的思路,谢谢!

6 楼

支持原创!!!!

7 楼

讲《数据结构与算法》有的书上,讲线性表或栈的时候,经常会以算术表达式求值或中缀表达式转换为逆波兰式,逆波兰式求值……为例子。
数据结构还真是有用啊

8 楼

呵呵 用ScriptControl不是很方便吗?

Option Explicit

Private Sub Form_Load()
  Dim str1 As String
  
  str1 = "3 * 7 - ((2 + 18)/5 + 6)"
  MsgBox str1 & " = " & Calc(str1)
End Sub

'计算公式
Public Function Calc(Formula As String) As Long
  Dim SC As Object
  Dim str1 As String
  
  Set SC = CreateObject("ScriptControl")
  With SC
    .Language = "VBScript"
    
    On Error Resume Next
    Calc = .Eval(Formula)
    
    If Err Then
      With .Error
        MsgBox "calc 计算错误:" & vbCrLf & vbCrLf & _
               "原因:" & .Description & vbCrLf & _
               "代码:" & .Number & vbCrLf & _
               "行号:" & .Line, vbCritical, .Source
      End With
    End If
  End With
  Set SC = Nothing
End Function

9 楼

检查括号的代码是不是不太正确?仅检查前括号和后括号的数目并不能完全保证结果正确,比如
5+6*(1+2))(-3
这个式子在你的代码这里能够通过,但是显然不正确。
我觉得可以设置一个变量,初始为0,遇到前括号+1,遇到后括号-1
这样判断式子不合法的依据是:
1.在任何时候,发现这个变量是负数
2.在检查完最后一个字符之后,这个变量不=0

10 楼

9楼的想法可取。只是检验和计算分别进行。
另一个思路是:
从前往后遇到左括号开始调用脱括号函数(实际就是求表达式值的函数自己,也就是递归);遇到右括号,报错。
在求表达式值里到“右括号”或者“表达式结尾”结束,这里自动给没有右括号的左括号在表达式为匹配。
例如:2+3*(5+9按照2+3*(5+9)计算。

我来回复

您尚未登录,请登录后再回复。点此登录或注册