# mathematical-expression 实现 数学表达式解析 C++ 篇
*C++ 技术栏*
使用 ME( mathematical-expression)数学表达式解析库 实现 C++ 中 数学表达式 的解析和计算。
## 目录
[TOC]

## mathematical-expression 介绍
在计算机中,本身并不存在数学表达式的概念,数学表达式其本身是在人类世界中对于逻辑的一种总结规范,计算机并不了解这些公式中的符号,因此对于数学表达式也需要一个编译器,就像编程语言到机器码之间的编译器一样,mathematical-expression 是一种针对数学公式解析的有效工具,能够解析包含嵌套函数,包含函数,数列步长累加等数学公式,返回值是一个数值的结果对象,同时也可以进行比较运算的操作,再进行比较的时候,返回值是一个布尔值结果对象。
### 获取到 ME 组件
ME 组件的获取 在 C++ 中是以动态库的形式存在的,因此您可以直接使用引入 dll 的方式来实现 C++ 中库的调用,下面就是一个示例。
#### ME 组件存储库
您可以在 [ME 组件存储库 历史存档馆](https://github.com/BeardedManZhao/mathematical-expression-cpp/releases "ME 组件存储库 历史存档馆") 中获取到您需要的版本对应的动态库文件,当然,您也可以直接使用源码进行编译,使得其可以在您的操作系统中使用!
#### 编译 ME 组件库
##### 下载源码
在这里我们以 Windows 系统中进行编译为例子,首先需要执行下面的命令来下载这个文件,其中的版本号可以更换!
```
wget https://github.com/BeardedManZhao/mathematical-expression-cpp/archive/refs/tags/1.0.1.1.zip
```
这样之后就可以获取到库的源码了,解压之后源码结构如下所示。
```
G:\My_Project\CLION\mathematical-expression-cpp>tree
卷 Barracuda 的文件夹 PATH 列表
卷序列号为 8CC1-0CB8
G:.
├─.idea
├─cmake-build-debug
│ ├─.cmake
│ │ └─api
│ │ └─v1
│ │ ├─query
│ │ └─reply
│ ├─CMakeFiles
│ │ ├─3.23.2
│ │ │ ├─CompilerIdC
│ │ │ └─CompilerIdCXX
│ │ ├─mathematical_expression_cpp.dir
│ │ └─Progress
│ └─Testing
│ └─Temporary
├─include
├─src
│ ├─core
│ │ ├─calculation
│ │ └─container
│ ├─dataContainer
│ ├─exceptional
│ └─utils
└─update
```
接下来我们开始进行项目构建和编译,在这里我们使用的C++开发工具是 Clion,编译的日志如下所示。
```
====================[ 清理 | Debug ]==============================================
"D:\Liming\MyApplication\CLion\CLion 2022.2.1\bin\cmake\win\bin\cmake.exe" --build G:\My_Project\CLION\mathematical-expression-cpp\cmake-build-debug --target clean -- -j 16
清理 已完成
====================[ 构建 | mathematical_expression_cpp | Debug ]================
"D:\Liming\MyApplication\CLion\CLion 2022.2.1\bin\cmake\win\bin\cmake.exe" --build G:\My_Project\CLION\mathematical-expression-cpp\cmake-build-debug --target mathematical_expression_cpp -- -j 16
[ 16%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/container/CalculationResults.cpp.obj
[ 22%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/PrefixExpressionOperation.cpp.obj
[ 44%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/Calculation.cpp.obj
[ 44%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/utils/StrUtils.cpp.obj
[ 50%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/dataContainer/MEStack.cpp.obj
[ 72%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/BooleanCalculationTwo.cpp.obj
[ 66%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/BracketsCalculationTwo.cpp.obj
[ 66%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/BracketsCalculation.cpp.obj
[ 88%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/BooleanCalculation.cpp.obj
[ 88%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/exceptional/MExceptional.cpp.obj
[ 88%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/mathematical_expression.cpp.obj
[ 88%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/NumberCalculation.cpp.obj
[ 88%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/FunctionManager.cpp.obj
[ 88%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/FunctionFormulaCalculation.cpp.obj
[ 88%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/utils/NumberUtils.cpp.obj
[ 88%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/CalculationConstant.cpp.obj
[ 94%] Building CXX object CMakeFiles/mathematical_expression_cpp.dir/src/core/calculation/CumulativeCalculation.cpp.obj
[100%] Linking CXX shared library libmathematical_expression_cpp.dll
[100%] Built target mathematical_expression_cpp
构建 已完成
```
编译操作结束,我们执行下面的命令就可以看到 “libmathematical_expression_cpp.dll” 文件了,这个文件就是我们编译好的动态库。
```
G:\My_Project\CLION\mathematical-expression-cpp>cd cmake-build-debug
G:\My_Project\CLION\mathematical-expression-cpp\cmake-build-debug>dir
驱动器 G 中的卷是 Barracuda
卷的序列号是 8CC1-0CB8
G:\My_Project\CLION\mathematical-expression-cpp\cmake-build-debug 的目录
2023/12/02 15:01 <DIR> .
2023/12/02 14:51 <DIR> ..
2023/12/02 14:51 <DIR> .cmake
2023/06/23 06:33 285,824 .ninja_deps
2023/06/23 06:33 10,360 .ninja_log
2023/06/23 06:33 18,474 build.ninja
2023/12/02 14:53 53,705 CMakeCache.txt
2023/12/02 15:01 <DIR> CMakeFiles
2023/12/02 14:53 1,691 cmake_install.cmake
2023/12/02 15:01 16,956,659 libmathematical_expression_cpp.dll
2023/12/02 15:01 3,027,692 libmathematical_expression_cpp.dll.a
2023/12/02 14:53 30,288 Makefile
2023/12/02 14:53 14,155 mathematical_expression_cpp.cbp
2023/12/02 14:51 <DIR> Testing
9 个文件 20,398,848 字节
5 个目录 2,684,821,766,144 可用字节
```
#### 装载 ME 库
您可以将您下载或者您手动编译好的库文件拷贝到您的 exe 文件的目录中,并且将头文件拷贝到您的项目中,头文件就是 ME 源码包中的 “include”目录,接下来将介绍一些导入 ME dll文件的方法。
##### Cmake 依赖脚本
###### 编写 cmake 脚本
如果您选择使用 cmake 那么下面就是一个 cmake 脚本的例子
```cmake
cmake_minimum_required(VERSION 3.23)
project(CppTest)
set(CMAKE_CXX_STANDARD 14)
add_executable(CppTest main.cpp)
# 设置头文件目录
include_directories(head include)
# 将链接库直接与程序文件存储在一起,使得程序文件不需要依赖额外的库,避免缺失dll
set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "-static")
# 链接动态库
target_link_libraries(${PROJECT_NAME}
G:/My_Project/CLION/CppTest/cmake-build-debug/libmathematical_expression_cpp.dll)
```
###### 将头文件以及dll库放置到对应的位置中
下面将分别展示项目结构,头文件目录结构,以及编译目录结构
```
PS G:\My_Project\CLION\CppTest> tree # 查看项目结构
卷 Barracuda 的文件夹 PATH 列表
卷序列号为 8CC1-0CB8
G:.
├─.idea
├─cmake-build-debug
│ ├─.cmake
│ │ └─api
│ │ └─v1
│ │ ├─query
│ │ └─reply
│ ├─CMakeFiles
│ │ ├─3.23.2
│ │ │ ├─CompilerIdC
│ │ │ │ └─tmp
│ │ │ └─CompilerIdCXX
│ │ │ └─tmp
│ │ ├─CMakeTmp
│ │ └─CppTest.dir
│ └─Testing
│ └─Temporary
└─head
PS G:\My_Project\CLION\CppTest> dir head # 查看头文件目录
目录: G:\My_Project\CLION\CppTest\head
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2023/6/23 6:33 1110 BooleanCalculation.h
-a---- 2023/6/23 6:33 1168 BooleanCalculationTwo.h
-a---- 2023/6/23 6:33 2025 BracketsCalculation.h
-a---- 2023/6/23 6:33 1188 BracketsCalculationTwo.h
-a---- 2023/6/23 6:33 1739 Calculation.h
-a---- 2023/6/23 6:33 1172 CalculationConstant.h
-a---- 2023/6/23 6:33 9518 CalculationResults.h
-a---- 2023/6/23 6:33 2857 ConstantRegion.h
-a---- 2023/6/23 6:33 1157 CumulativeCalculation.h
-a---- 2023/6/23 6:33 824 FunctionFormulaCalculation.h
-a---- 2023/6/23 6:33 2510 FunctionManager.h
-a---- 2023/6/23 6:33 3087 mathematical_expression.h
-a---- 2023/6/23 6:33 1308 MEStack.h
-a---- 2023/6/23 6:33 767 MExceptional.h
-a---- 2023/6/23 6:33 2382 NumberCalculation.h
-a---- 2023/6/23 6:33 1202 PrefixExpressionOperation.h
-a---- 2023/6/23 6:33 3425 Utils.h
PS G:\My_Project\CLION\CppTest> dir .\cmake-build-debug\ # 查看编译结果目录 里面有 libmathematical_expression_cpp.dll
目录: G:\My_Project\CLION\CppTest\cmake-build-debug
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2023/12/2 14:41 .cmake
d----- 2023/12/2 15:05 CMakeFiles
d----- 2023/12/2 14:42 Testing
-a---- 2023/12/2 15:05 10278 build.ninja
-a---- 2023/12/2 14:42 16919 CMakeCache.txt
-a---- 2023/12/2 14:42 1631 cmake_install.cmake
-a---- 2023/12/2 15:01 16956659 libmathematical_expression_cpp.dll
PS G:\My_Project\CLION\CppTest>
```
##### 查看 ME 库的版本 测试是否成功导入文件
```c++
#include "mathematical_expression.h"
int main(){
system("chcp 65001");
// 打印 mathematical_expression 的版本信息
cout << mathematical_expression::getVERSION() << endl;
}
```
下面是运行结果,成功打印出来了版本信息,代表库文件导入成功!!!
```
Active code page: 65001
1.0.1.1-mathematical_expression-C++
进程已结束,退出代码0
```
### ME 组件结构
ME 组件主要由三个部分组成,分别是数据存储对象,以及,解析与计算对象,还有计算管理者,他们分别负责不同的任务和位置,共同配合,实现强大的计算操作。
```flow
st=>start: 数学表达式
op0=>operation: 从管理者获取到计算组件
op1=>operation: 检查表达式
op2=>operation: 使用解析与计算对象-解析表达式
op3=>operation: 使用解析与计算对象-计算表达式
op4=>operation: 封装计算结果与计算信息
error=>operation: 发生异常
ok=>operation: 计算完毕,封装结果
cond0=>condition: 检查是否通过?
cond1=>condition: 是否能够支持这类数学表达式的解析?
cond2=>condition: 是否实现共享池?
cond3=>condition: 解析是否通过?
cond4=>condition: 计算操作正常?
e=>end: 得出结果或抛出异常
st->op0->op1->cond0->e
cond0(no)->error
cond1(no)->error
cond2(no)->cond3->opt2
cond3(no)->error
cond4(no)->error
cond0(yes)->cond1
cond1(yes)->cond2
cond2(yes)->cond4
cond3(yes)->cond4
cond4(yes)->op4->ok
```
接下来我们将进行实际的演示,讲述如何在 Java 中快速且方便的处理数学表达式。
#### 无括号数学表达式计算组件
此组件顾名思义,就是用于没有括号的数学表达式的解析和计算中,此计算组件是所有计算组件的祖先,具有更简单的结构以及计算性能
```flow
st=>start: 开始计算
cond1=>condition: 是否为高优先级
cond2=>condition: 是否提取完毕
opt1=>operation: 提取运算符
opt2=>operation: 提取操作数
opt3=>operation: 计算临时结果
opt4=>operation: 结果汇总
opt5=>operation: 临时存储
e=>end: 数值结果封装
st->cond2
cond2(no)->opt2->opt1->cond1
cond2(yes)->opt4
cond1(yes)->opt3->opt5->opt2->opt1->cond1
cond1(no)->opt5->opt2->opt1->cond1
```
但面对复杂的计算表达式,此计算组件将无法体现出其灵活性,下面是有关该表达式的一个操作示例。
```java
#include "mathematical_expression.h"
int main(){
system("chcp 65001");
// 准备一个数学表达式
string s1 = "1 + 20 * 2 + 4";
// 准备 ME 组件对象 的门户类
mathematical_expression me;
// 获取到无括号表达式计算组件
ME::PrefixExpressionOperation prefixExpressionOperation = me.getPrefixExpressionOperation();
// 开始进行检查
prefixExpressionOperation.check(s1);
// 开始进行计算 这里不仅仅可以使用 calculation 还可以使用 左移运算符
ME::CalculationNumberResults r1 = prefixExpressionOperation.calculation(s1);
r1 = prefixExpressionOperation. << s1;
// 打印计算结果
cout << "计算层数:" << r1.getResultLayers() << "\t计算结果:" << r1 << "\t计算来源:" << r1.getCalculationSourceName() << endl;
}
```
最终计算结果
```
Active code page: 65001
计算层数:1 计算结果:45 计算来源:PrefixExpressionOperation
进程已结束,退出代码0
```
#### 有括号的数学表达式计算
带有括号的数学计算表达式,是最常用的一种计算组件,其能够实现有效的数学表达式优先级计算操作,计算的复杂度也不会太高,下面就是结果
```flow
st=>start: 开始计算
cond1=>condition: 是否包含括号
opt1=>operation: 提取子表达式
opt2=>operation: 无括号表达式计算 1or2 级计算
opt3=>operation: 结果汇总
e=>end: 数值结果封装
st->cond1->e
cond1(yes)->opt1->cond1
cond1(no)->opt2->opt3->e
```
在库中诸多的计算组件都是基于此组件进行拓展的,下面就是一个使用示例。
```java
#include "mathematical_expression.h"
int main(){
system("chcp 65001");
// 准备一个数学表达式
string s1 = "1 + 20 * (2 + 4)";
// 准备 ME 组件对象 的门户类
mathematical_expression me;
// 获取到无括号表达式计算组件
ME::BracketsCalculationTwo bracketsCalculationTwo = me.getBracketsCalculation2();
// 开始进行检查
bracketsCalculationTwo.check(s1);
// 开始进行计算
ME::CalculationNumberResults r1 = bracketsCalculationTwo << s1;
// 打印计算结果
cout << "计算层数:" << r1.getResultLayers() << "\t计算结果:" << r1 << "\t计算来源:" << r1.getCalculationSourceName() << endl;
}
```
最终计算结果
```
Active code page: 65001
计算层数:2 计算结果:121 计算来源:BracketsCalculation
进程已结束,退出代码0
```
#### 比较运算表达式
基于 有括号 计算组件的一种拓展,在这里我们可以针对两个数学表达式进行比较,在这里的比较操作中,能够实现有效的比较运算,返回值是 布尔 类型的结果对象,它的计算过程如下所示。
```flow
st=>start: 开始计算
cond1=>condition: 左右解析(yes代表左)
opt1=>operation: 获取左边表达式
opt2=>operation: 获取右边表达式
opt3=>operation: 有括号表达式计算 N级计算
e=>end: 布尔比较计算 N+1级计算
st->cond1
cond1(yes)->opt1->opt3->e
cond1(no)->opt2->opt3->e
```
```java
public class MAIN {
public static void main(String[] args) throws WrongFormat {
// 获取一个计算数学比较表达式的组件
final Calculation instance = Mathematical_Expression.getInstance(Mathematical_Expression.booleanCalculation2);
// 创建3个表达式
String s1 = "1 + 2 + 4 * (10 - 3)";
String s2 = "2 + 30 + (2 * 3) - 1";
String s3 = "1 + 3 * 10";
extracted(instance, s1 + " > " + s2);// false
extracted(instance, s1 + " < " + s2);// true
extracted(instance, s1 + " = " + s3);// true
extracted(instance, s1 + " == " + s3);// true
extracted(instance, s1 + " != " + s3);// false
extracted(instance, s1 + " <> " + s3);// false
extracted(instance, s1 + " <= " + s3);// true
extracted(instance, s1 + " >= " + s3);// true
extracted(instance, s1 + " != " + s2);// true
extracted(instance, s1 + " <> " + s2);// true
}
private static void extracted(Calculation booleanCalculation2, String s) throws WrongFormat {
// 检查表达式是否有错误
booleanCalculation2.check(s);
// 开始计算结果
CalculationBooleanResults calculation = (CalculationBooleanResults) booleanCalculation2.calculation(s);
// 打印结果数值
System.out.println(
"计算层数:" + calculation.getResultLayers() + "\t计算结果:" + calculation.getResult() +
"\t计算来源:" + calculation.getCalculationSourceName()
);
}
}
```
下面就是计算结果,可以看到这里返回的结果是一个 布尔 类型的结果对象
```
计算层数:4 计算结果:false 计算来源:booleanCalculation2
计算层数:4 计算结果:true 计算来源:booleanCalculation2
计算层数:3 计算结果:true 计算来源:booleanCalculation2
计算层数:3 计算结果:true 计算来源:booleanCalculation2
计算层数:3 计算结果:false 计算来源:booleanCalculation2
计算层数:3 计算结果:false 计算来源:booleanCalculation2
计算层数:3 计算结果:true 计算来源:booleanCalculation2
计算层数:3 计算结果:true 计算来源:booleanCalculation2
计算层数:4 计算结果:true 计算来源:booleanCalculation2
计算层数:4 计算结果:true 计算来源:booleanCalculation2
进程已结束,退出代码0
```
#### 函数计算表达式
在 ME 库中,允许使用一些函数来进行计算操作,数学表达式中的函数在这里也可以实现,通过自定义实现函数的方式,能够封装计算逻辑,且有效的提升灵活性,其计算复杂度并不高,也是比较常用的计算组件。
```flow
st=>start: 开始计算
cond0=>condition: 函数是否提取完毕
cond1=>condition: 函数是否存在
opt1=>operation: 提取函数
opt2=>operation: 提取函数形参
opt3=>operation: 有括号表达式计算 N级计算
opt4=>operation: 计算结果传递
opt5=>operation: 函数内部计算
opt6=>operation: 结果存储
opt7=>operation: 有括号表达式计算 结果汇总
error=>operation: 发生异常
ok=>operation: 包装结果
st->opt1->cond1
cond1(yes)->opt2->opt3->opt4->opt5->opt6->cond0
cond1(no)->error
cond0(yes)->opt7
cond0(no)->opt1
```
接下来就是相关的使用演示!!!
```c++
#include <mathematical_expression.h>
#include "FunctionManager.h"
int main() {
system("chcp 65001");
// 准备函数 这里的函数的作用是将参数 * 2
auto myFun = [](const double *v) {
return *v * 2;
};
// 注册函数 将我们的函数注册成为 DoubleValue 的名称
ME::FunctionManager::append("DoubleValue", myFun);
// 构建一个数学表达式,表达式中使用到了函数 DoubleValue
string s = "2 * DoubleValue(2 + 3) + 1";
// 获取到 数学表达式解析库
mathematical_expression me;
// 获取到函数表达式计算组件
auto functionFormulaCalculation = me.getFunctionFormulaCalculation();
// 检查数学表达式
functionFormulaCalculation.check(s);
// 计算出结果
ME::CalculationNumberResults results = functionFormulaCalculation << s;
// 将结果打印出来
cout << "计算层数:" << results.getResultLayers() << "\t计算结果:" << results << "\t计算来源:" << results.getCalculationSourceName() << endl;
}
```
- 运行结果
```
Active code page: 65001
计算层数:1 计算结果:21 计算来源:BracketsCalculation
进程已结束,退出代码0
```
#### 注意事项
1.0.2 版本中 针对函数的注册操作不能向后兼容,如果是在1.0.2版本以以后的版本 请使用下面的方式注册函数
```
// 准备函数 将函数的形参类型 由 double* 更改为 ME::MEStack<double> 即可 因为 ME::MEStack<double> 具有更大的灵活性
auto myFun =[](const ME::MEStack<double>& v) {
double res = 0;
for (int i = 0; i < v.size(); ++i){
res += v.get(i);
}
return res;
};
// 注册函数 将我们的函数注册成为 DoubleValue 的名称
ME::FunctionManager::append("sum", myFun);
```
#### 多参函数计算组件
从 ME 库 C++ API 1.0.2 版本开始 针对函数数学表达式的计算将不再限制于只能传递一个参数,而是可以传递无数个参数,每个参数会在函数中以函数形参的格式进入到函数对象并进行计算,接下来就是相关的实际演示。
```java
#include <mathematical_expression.h>
#include "FunctionManager.h"
int main() {
system("chcp 65001");
// 准备函数 这里的函数的作用是将 3 个参数求和
auto myFun = [](const ME::MEStack<double>& v) {
double res = 0;
for (int i = 0; i < v.size(); ++i){
res += v.get(i);
}
return res;
};
// 注册函数 将我们的函数注册成为 DoubleValue 的名称
ME::FunctionManager::append("sum", myFun);
// 构建一个数学表达式,表达式中使用到了函数 DoubleValue
string s = "2 * sum(2 + 3, 1 + 20, 10 + (1 - 2)) + 1";
// 获取到 数学表达式解析库
mathematical_expression me;
// 获取到函数表达式计算组件
auto functionFormulaCalculation = me.getFunctionFormulaCalculation2();
// 检查数学表达式
functionFormulaCalculation.check(s);
// 计算出结果
ME::CalculationNumberResults results = functionFormulaCalculation << s;
// 将结果打印出来
cout << "计算层数:" << results.getResultLayers() << "\t计算结果:" << results << "\t计算来源:" << results.getCalculationSourceName() << endl;
}
```
在这里就是相关的计算结果
```
Active code page: 65001
计算层数:1 计算结果:71 计算来源:BracketsCalculation
进程已结束,退出代码0
```
=未完待续=
----
相关文章
- [《mathematical-expression 实现 数学表达式解析 Java 篇》](https://www.lingyuzhao.top/?/linkController=/articleController&link=94267819 "mathematical-expression 实现 数学表达式解析 Java 篇")
------
***操作记录***
作者:[root](https://www.lingyuzhao.top//index.html?search=1 "root")
操作时间:2024-02-07 19:19:37 星期三
事件描述备注:保存/发布
中国 天津
[](如果不需要此记录可以手动删除,每次保存都会自动的追加记录)