首页/新闻资讯/正文详情

C++实现四则运算器(无括号)

发布时间:2026/11/4 21:10:33 来源:云帆数科 栏目:资讯中心
C++实现四则运算器(无括号)
前言「无括号四则运算器」要求解析只包含加、减、乘、除、整数和空格的表达式按先乘除后加减、同级从左到右的规则求值。它比带括号的版本少了一层「括号递归」但优先级、一元负号、多位数、除零这些边界一个都不少是练习递归下降recursive descent的好题目。常见误解有三类。第一从左到右顺序扫描、遇到运算符就立刻计算这样1 2 * 3会得到 9 而不是 7。第二只读一位数字遇到123只取1。第三把一元负号当成二元减号处理2 * -3在乘号后面遇到-时解析失败。本文用递归下降实现把表达式拆成「表达式 → 项 → 因子」三层每层只处理自己那一级的运算符优先级由调用层次天然保证。代码以 C17 为准使用long long做整数运算文末会说明溢出与除零的处理边界。一、文法与优先级把表达式写成文法expr : term (( | -) term)*term : factor ((* | /) factor)*factor : ( | -)* numbernumber : digitexpr处理加减term处理乘除factor处理一元正负号和数字。调用关系是expr → term → factor越往下优先级越高。每一层用循环处理「同级运算符」因此同级运算符自然左结合10 - 3 - 2先算10 - 3再减 2结果是 5而不是10 - (3 - 2)。这种结构的好处是加括号只需要在factor里增加一条「遇到左括号就递归调用expr再匹配右括号」的分支其他层不用改。本文不实现括号但保留这个扩展点。数字可以有多位解析时循环读取连续数字用value value * 10 digit累加。空白字符在每一层读取前跳过因此1 2 *3和1 2 * 3等价。空白只允许出现在记号之间不能出现在数字中间12 3会被解析成两个数随后因缺少运算符而报错。二、递归下降的实现结构用一个Parser类保存输入字符串的引用和当前下标pos_。每个解析函数都从pos_当前位置开始读取自己负责的部分并把pos_推进到下一个未处理字符。parseExpr调用parseTerm得到左值。循环跳过空白若当前字符是或-记录运算符并前进调用parseTerm得到右值执行加减否则退出循环。返回累计结果。parseTerm与parseExpr同构只是运算符换成*、/调用的下层换成parseFactor。除法要先检查右值是否为 0整数除法向零截断-7 / 2得到 -3这是 C 的规则不要按数学上的「向下取整」理解。parseFactor跳过空白。若当前是或-前进并递归调用自身返回正/负结果。这允许--5、-5这类写法。否则必须是数字循环读取连续数字累加成整数。若一个数字都没有抛出解析错误。最外层parse调用parseExpr后还要跳过空白并检查pos_是否到达末尾。若还有剩余字符例如1 2abc说明输入非法不能默默忽略。三、边界除零、溢出与非法字符除零整数除以 0 是未定义行为undefined behavior, UB必须在除法前检查。示例用异常报告错误调用方捕获后打印提示。不要用「返回 0」掩盖错误那会让1/0看起来像合法结果。溢出long long的加减乘都可能溢出有符号溢出是 UB。示例在解析数字时检查了「乘 10 加 digit」是否超过LLONG_MAX但运算符层面的溢出没有逐一检查因此要求输入的中间结果落在long long范围内。生产代码若不能保证这一点应改用大整数库或在每次加减乘前做范围检查不要依赖「溢出会回绕」这种未定义行为。另外LLONG_MIN / -1也会溢出示例单独拦截。非法字符数字解析失败、运算符后没有操作数、表达式末尾有多余字符都应该报错。错误信息至少包含位置方便定位。示例用std::runtime_error抛出主函数捕获后输出。空表达式parseFactor在没有任何数字时抛错因此空串、只有空白的串都会失败不会返回 0。一元负号与二元减号2-3中的-由parseExpr处理2*-3中的-出现在parseFactor开头由一元分支处理。两者靠「当前位置在哪个解析层」区分不需要额外的词法状态。四、完整实现// 适用于 C17#include cctype#include iostream#include limits#include stdexcept#include stringclass Parser {public:explicit Parser(const std::string text) : text_(text) {}long long parse() {const long long value parseExpr();skipSpaces();if (pos_ ! text_.size()) {throw std::runtime_error(unexpected trailing characters);}return value;}private:void skipSpaces() {while (pos_ text_.size() std::isspace(static_castunsigned char(text_[pos_]))) {pos_;}}long long parseExpr() {long long value parseTerm();for (;;) {skipSpaces();if (pos_ text_.size() ||(text_[pos_] ! text_[pos_] ! -)) {break;}const char op text_[pos_];const long long rhs parseTerm();value (op ) ? value rhs : value - rhs;}return value;}long long parseTerm() {long long value parseFactor();for (;;) {skipSpaces();if (pos_ text_.size() ||(text_[pos_] ! * text_[pos_] ! /)) {break;}const char op text_[pos_];const long long rhs parseFactor();if (op *) {value * rhs;} else {if (rhs 0) {throw std::runtime_error(division by zero);}if (value std::numeric_limitslong long::min() rhs -1) {throw std::runtime_error(integer overflow);}value / rhs;}}return value;}long long parseFactor() {skipSpaces();if (pos_ text_.size() (text_[pos_] || text_[pos_] -)) {const char sign text_[pos_];const long long value parseFactor();return (sign -) ? -value : value;}if (pos_ text_.size() ||!std::isdigit(static_castunsigned char(text_[pos_]))) {throw std::runtime_error(number expected);}long long value 0;while (pos_ text_.size() std::isdigit(static_castunsigned char(text_[pos_]))) {const int digit text_[pos_] - 0;if (value (std::numeric_limitslong long::max() - digit) / 10) {throw std::runtime_error(number too large);}value value * 10 digit;pos_;}return value;}const std::string text_;std::size_t pos_ 0;};int main() {std::string line;if (!std::getline(std::cin, line)) {return 1;}try {Parser parser(line);std::cout parser.parse() \n;} catch (const std::exception e) {std::cout error: e.what() \n;return 1;}return 0;}按语法推演几个输入1 2 * 3先由parseTerm读出 2、遇到*读出 3得到 6再由parseExpr加 1结果是 710 - 3 - 2在parseExpr的循环里先算 7再减 2结果是 52 * -3 10在parseTerm中把-3作为因子得到 -6再加 10结果是 410 / 3是整数除法结果是 31 / 0在除法前抛异常。代码中的加减乘没有做逐次溢出检查这是有意的简化完整检查会让示例膨胀一倍而且需要处理LLONG_MIN的各种边界。文章已经把「中间结果必须在long long范围内」写成前提若你的输入可能接近边界必须在value rhs、value - rhs、value * rhs之前加检查不能假定溢出是安全的。常见坑点1. 从左到右直接求值❌ 扫描到运算符就立刻用当前累计值计算1 2 * 3得到 9。✅ 分层解析parseTerm先算乘除parseExpr再算加减。2. 只读一位数字❌value text_[pos_] - 0; pos_;123只得到 1。✅ 用while读取连续数字累加value * 10 digit。3. 不跳过空白❌ 遇到空格就报「非法字符」。✅ 每层读取前skipSpaces并注意std::isspace的参数要转unsigned char。4. 不支持一元负号❌2 * -3在乘号后调用数字解析直接失败。✅ 在parseFactor开头处理/-并递归。5. 除法不检查零❌value / rhs;当rhs 0时是 UB。✅ 先判断rhs 0并报错。6. 忽略尾部多余字符❌parseExpr返回后直接输出12abc被当成 3。✅ 解析完检查pos_ text_.size()。7. 把整数除法当浮点除法❌ 认为7 / 2是 3.5。✅ 整数相除向零截断需要小数就改用double或定点表示。8. 认为溢出会安全回绕❌ 依赖long long溢出后的值做判断。✅ 有符号溢出是 UB加检查或改用大整数库。总结层次负责运算符expr加减、-左结合term乘除*、/左结合factor一元正负、数字、-前缀边界处理多位数循环累加空白每层跳过除零除法前检查尾部字符解析后校验溢出解析检查 前提约束无括号四则运算器的骨架就是三层递归下降优先级高的层被优先级低的层调用同级运算符用循环实现左结合。把「多位数、空白、一元符号、除零、尾部校验」这五个边界补上表达式求值就不会在看起来简单的输入上出错。

相关推荐

Python+UNet图像分割实战:从数据掩膜到IoU推理排错
Python+UNet图像分割实战:从数据掩膜到IoU推理排错

简介:面向具备一定Python与深度学习基础、希望用U-Net完成图像分割实践的开发者与学习者,这份压缩包围绕分割任务的数据准备、模型搭建、损失函数选择、优化器配置、训练验证与推理预测展开,并聚焦大尺寸图像分块预测后的平滑融合处理&#x… · 2026/11/3 16:43:00

WPF TreeView拖拽实现:插入线、自动展开与MVVM节点移动
WPF TreeView拖拽实现:插入线、自动展开与MVVM节点移动

简介:面向希望掌握WPF TreeView拖放交互的开发者,这份示例项目聚焦层次结构节点的拖拽重排与数据移动,补充原生TreeView缺失的拖放支持,适合已具备WPF事件与数据绑定基础、想深入DragDrop API的中级读者。压缩包共12个文件、约11K… · 2026/11/3 18:08:36

JSP相册系统实战:上传、缩略图、分页与越权防护
JSP相册系统实战:上传、缩略图、分页与越权防护

简介:一套基于JSP的相册管理系统源码,面向Java Web初学者、课程设计或毕业设计参考者,围绕用户登录、照片上传、相册分类、评论互动与后台维护等常见需求,呈现JSP页面、Servlet与MySQL数据库协作的完整实现思路。压缩包共115个文件… · 2026/11/3 21:01:48

DeepSeek V4.1 Flash 技术解读:轻量化 AI 模型的高效推理之道
DeepSeek V4.1 Flash 技术解读:轻量化 AI 模型的高效推理之道

1. 先说清楚:这个 V4.1 Flash 到底是什么来头 最近打开技术社区和聊天群,到处都是 DeepSeek V4.1 Flash、Flash Attention、Flash 这个 Flash 那个的讨论。我本来以为又是哪家厂商整了个营销新词,结果蹲了一圈消息源,发现这事比我… · 2026/11/4 21:10:13

MongoDB分片集群性能优化:Zone划分与读写分离实践
MongoDB分片集群性能优化:Zone划分与读写分离实践

1. MongoDB分片集群性能优化概述作为一名长期奋战在数据库运维一线的工程师,我见过太多团队在MongoDB分片集群使用初期表现良好,但随着数据量增长却陷入性能泥潭的案例。分片集群虽然提供了理论上无限的水平扩展能力,但如果不掌握正确的调优方… · 2026/11/4 21:10:13

深入解析STL容器:原理、实现与性能优化
深入解析STL容器:原理、实现与性能优化

1. STL容器概述与设计哲学 STL(Standard Template Library)作为C标准库的核心组成部分,其容器类模板的设计体现了泛型编程的精华。在实际工程中,我们经常直接使用vector、map这些容器,但真正理解它们的底层实现机制&am… · 2026/11/4 21:10:13

多智能体系统通信协议:MCP、A2A与ACP技术解析与应用
多智能体系统通信协议:MCP、A2A与ACP技术解析与应用

1. 多智能体系统协议演进背景在分布式人工智能领域,多智能体系统(MAS)的协同效率直接取决于通信协议的标准化程度。早期智能体间通信存在三大痛点:首先是协议碎片化导致跨平台协作困难,不同研究团队开发的智能体往往采用私有通信格式&#xf… · 2026/11/4 21:10:13

顺序表在通讯录项目中的工程实践与性能优化
顺序表在通讯录项目中的工程实践与性能优化

1. 顺序表基础与通讯录项目背景 顺序表作为数据结构中最基础的线性存储结构,在C语言中通常通过数组实现。我在处理嵌入式设备日志系统时首次深刻体会到它的价值——当需要快速存取固定格式的传感器数据时,顺序表相比链表能减少80%以上的访问时间。通讯录… · 2026/11/4 21:10:13

INCA汽车标定入门:安装、A2L/S19导入与CAN测量实操
INCA汽车标定入门:安装、A2L/S19导入与CAN测量实操

简介:《INCA标定软件使用教程》面向汽车标定初学者及需要熟悉INCA操作的汽车电子工程师,围绕软件安装、许可证配置、实验环境建立、标定工程设计与数据录制等环节展开,帮助读者理解A2L与s19文件导入、CAN设备连接、变量选取和示波器观察等基础… · 2026/11/4 21:09:12

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38

AI辅助技术栈评估:提升研发决策效率60%
AI辅助技术栈评估:提升研发决策效率60%

1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17

从生成Token到直接决策:Jev如何重塑AI Agent工程实践
从生成Token到直接决策:Jev如何重塑AI Agent工程实践

1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17

32G统一内存跑大模型:内存账、TPS实测与端云分流
32G统一内存跑大模型:内存账、TPS实测与端云分流

想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码