1. 项目概述用Arduino打造你的第一台桌面计算器如果你对嵌入式开发感兴趣想找一个既能练手、又有实际成果还能把硬件和软件知识串起来的项目那这个基于Arduino的简易计算器绝对是个绝佳的选择。它不像流水灯那样简单也不像机器人那样复杂正好卡在中间那个“有挑战但能搞定”的甜点区。我当年就是从类似的项目入坑一步步摸清了微控制器、传感器和显示模块之间是怎么“对话”的。这个项目的核心就是用一块Arduino板子作为大脑搭配一个4x4矩阵键盘作为输入设备再用一块I2C接口的LCD屏幕来显示结果最终实现一个能进行加、减、乘、除甚至带小数点运算的计算器。听起来是不是很像我们小时候用的那种基础款计算器没错它的逻辑就是那样纯粹。但别小看它从读取一个按键的按下到在屏幕上显示一个数字再到处理“12*3”这样的表达式虽然我们这个版本为了简化暂时不处理运算优先级这中间每一步都涉及到嵌入式系统开发最核心的概念输入/输出I/O控制、中断或轮询、数据处理和用户界面UI更新。对于初学者这是理解“代码如何驱动硬件”的完美范例对于有一定经验的开发者你可以把它当作一个框架去挑战实现更复杂的功能比如支持负数、括号、三角函数甚至把它做成一个便携的桌面小工具。接下来我会带你从硬件选型、电路连接一直写到代码的每一行逻辑并分享我在调试过程中踩过的那些坑和总结出的技巧。2. 核心硬件选型与电路设计解析动手之前搞清楚我们用的“积木”是什么以及为什么选它们比盲目接线重要得多。这个项目的硬件架构非常经典主控单元MCU 输入模块 输出模块。2.1 主控单元为什么是ArduinoArduino Uno是绝大多数人的首选原因很实在生态成熟资料、库、社区支持都是最丰富的遇到问题几乎一定能搜到答案。引脚充足我们只需要用到少数几个数字和模拟引脚Uno的14个数字I/O口和6个模拟输入口绰绰有余。5V逻辑电平这很重要因为我们选用的LCD模块和许多传感器都是5V工作电压直接连接无需电平转换省去很多麻烦。 当然如果你手头是Arduino Nano、Leonardo甚至ESP32只要它们有标准的Arduino引脚和5V输出能力也完全没问题。核心在于其兼容Arduino IDE和库生态系统。2.2 输入模块One Pin Keypad的巧思与替代方案原文提到了一个非常有趣的模块One Pin Keypad。它的设计极其巧妙将整个4x4矩阵键盘16个按键的检测通过一系列电阻网络压缩到只需要一个模拟输入引脚Analog Pin。原理是每个按键被按下时会形成一个独特的分压电路在模拟引脚上产生一个特定的电压值ADC读数。代码中预存一组这些电压值的“阈值”通过轮询读取模拟值并匹配阈值就能判断是哪个键被按下了。优点极致节省I/O口仅占用1个引脚这在引脚紧张的项目中是巨大优势。电路简洁外部连线少布线清爽。需要注意的坑依赖精确的ADCArduino的ADC模数转换器参考电压默认是5V其精度和稳定性会受到电源噪声的影响。因此每个模块甚至每块Arduino板都需要进行单独的阈值校准否则容易出现按键误触发或失灵。无法实现“多键同时按下”这是电阻分压式键盘的物理限制但对于计算器来说这通常不是问题。如果没有这个模块怎么办完全可以用最传统的4x4矩阵键盘直接连接。这需要占用8个数字I/O口4行4列通过行列扫描法来检测按键。虽然多用了一些引脚但好处是原理直观、库支持成熟如Keypad库且抗干扰能力更强。对于本项目如果使用传统矩阵键盘你需要调整代码中的按键读取部分但整体计算逻辑完全通用。2.3 输出模块I2C LCD为何是首选1602液晶屏16列2行很常见但直接驱动它需要6个以上的I/O口数据线控制线。I2C接口转换板的出现解决了这个问题。I2C通信仅需两根线SDA-数据线 SCL-时钟线就能完成通信极大地节省了引脚。这两根线在Arduino Uno上对应A4SDA和A5SCL。地址可调大部分I2C LCD模块背面有一个跳线帽或焊点可以改变其I2C地址通常是0x27或0x3F这允许你在同一总线上挂载多个设备。库支持LiquidCrystal_I2C库让驱动LCD变得异常简单几行代码就能初始化并显示文字。接线时的关键点电源一定别接反仔细对照模块引脚标识VCC接5V GND接GND。I2C地址这是最常见的调试问题。务必使用扫描代码后文会提供确认你的LCD模块的确切地址。对比度调节模块上通常有一个蓝色的电位器用螺丝刀旋转它可以调节屏幕显示的深浅。如果上电后屏幕只有一排方块或完全没显示先调这个电位器。2.4 完整电路连接图与供电考量整个系统的连接思路是“星型拓扑”所有模块的电源VCC和地GND都接到Arduino的5V和GND引脚上形成共同的参考地。信号线则各司其职。连接清单基于One Pin Keypad方案Arduino 5V-面包板正极总线- I2C LCD的VCC引脚 One Pin Keypad模块的VCC引脚。Arduino GND-面包板负极总线- I2C LCD的GND引脚 One Pin Keypad模块的GND引脚。Arduino A4 (SDA)- I2C LCD的SDA引脚。Arduino A5 (SCL)- I2C LCD的SCL引脚。Arduino 某一个模拟引脚如A0- One Pin Keypad模块的信号输出引脚SIG。注意在给面包板接线时尽量使用不同颜色的跳线区分电源红色、地黑色和信号线黄、绿等并在连接前断开Arduino的USB线避免短路。所有连接务必在断电状态下进行。供电通过USB线连接电脑或一个5V/1A的手机充电器供电就足够了。整个系统功耗很低LCD背光和键盘模块的电流都很小。3. 软件架构与核心代码逐行解读硬件是躯体软件是灵魂。这个计算器的代码逻辑是一个典型的事件驱动状态机。它不断检测是否有按键事件发生然后根据当前计算器的状态如正在输入第一个数、等待运算符、正在输入第二个数、显示结果来决定如何处理这个按键。3.1 程序骨架与库引入// 1. 引入必要的库 #include Wire.h // I2C通信必备库 #include LiquidCrystal_I2C.h // 驱动I2C LCD // 2. 定义硬件连接引脚和参数 #define KEYPAD_PIN A0 // One Pin Keypad连接的模拟引脚 #define LCD_ADDR 0x27 // 你的LCD的I2C地址可能是0x3F #define LCD_COLS 16 // LCD列数 #define LCD_ROWS 2 // LCD行数 // 3. 初始化LCD对象 LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS); // 4. 全局变量定义 float operand1 0; // 第一个操作数 float operand2 0; // 第二个操作数 char operation \0; // 运算符 (, -, *, /) bool isFirstOperand true; // 标志当前是否在输入第一个数 bool hasDecimal false; // 标志当前输入的数是否已包含小数点 float decimalPlace 0.1; // 输入小数点后用于计算小数位的变量 String displayString ; // 当前屏幕上显示的字符串 // 5. 按键阈值数组 - 这是需要你根据自己模块校准的关键数据 // 数组下标0-15对应键盘上的16个键通常顺序是0-9, A-D, *, #, 但需根据你的键盘布局映射 int myThresholds[16] {50, 150, 250, 350, 450, 550, 650, 750, 850, 950, 1050, 1150, 1250, 1350, 1450, 1550}; // 注意以上是示例值你必须用自己的校准值替换关键解读Wire.h是Arduino内置的I2C库LiquidCrystal_I2C.h需要额外安装。你可以通过Arduino IDE的库管理器搜索安装。LCD_ADDR是第一个需要你亲自确认的参数。上传一个I2C地址扫描程序就能找到。使用float类型来存储操作数是为了支持小数运算。String类型用于方便地构建显示内容。myThresholds数组是项目的“钥匙”。里面的16个数值分别对应16个按键被按下时analogRead(KEYPAD_PIN)读到的近似值。你必须用后文介绍的校准程序来获取你自己模块的这组值。3.2 按键读取与映射函数这是处理One Pin Keypad的核心函数。// 函数读取当前按下的键并返回对应的字符 char readKeypad() { int sensorValue analogRead(KEYPAD_PIN); // 添加一个死区阈值防止噪声误触发 if (sensorValue 50) { // 假设无按键时读数小于50 return \0; // 返回空字符表示无按键 } // 遍历阈值数组找到最接近的匹配 for (int i 0; i 16; i) { // 允许一个误差范围比如±20。这个范围需要根据你模块的稳定性调整 if (abs(sensorValue - myThresholds[i]) 20) { delay(50); // 简单的软件防抖防止一次按下被识别多次 // 将索引映射为实际的字符 // 这个映射关系取决于你键盘上按键的物理布局和你的定义 // 例如一个典型的计算器映射可能是 // 索引: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 // 字符: 7,8,9,/,4,5,6,*,1,2,3,-,C,0,., char keyMap[16] {7, 8, 9, /, 4, 5, 6, *, 1, 2, 3, -, C, 0, ., }; return keyMap[i]; } } return \0; // 未匹配到任何已知键 }实操心得abs(sensorValue - myThresholds[i]) 20这里的20是误差容限。如果发现某些键不灵敏或串键可以适当增大这个值比如到30但太大会导致不同键之间容易混淆。校准做得越准这个值就可以设得越小响应就越精准。delay(50)是软件消抖。机械按键在闭合瞬间会产生物理抖动导致电压快速波动ADC会读到一系列值。延时一小段时间等抖动过去再读取能有效避免一次按压被识别为多次。对于用户体验要求高的场景可以用更高级的“状态机消抖”算法但这里简单延时足够用。映射关系keyMap这是连接物理按键和逻辑功能的桥梁。你必须根据你键盘上按键的实际顺序或者你贴的标签来定义这个数组。最好的方法是写一个简单的测试程序每按一个键就在串口监视器打印出其索引i然后你记录下每个位置对应的键。3.3 核心状态机setup()与loop()setup()函数负责一次性初始化。void setup() { Serial.begin(9600); // 初始化串口用于调试输出非常关键 lcd.init(); // 初始化LCD lcd.backlight(); // 打开背光 lcd.setCursor(0, 0); lcd.print(Arduino Calc); // 显示开机信息 lcd.setCursor(0, 1); lcd.print(Ready...); delay(1000); lcd.clear(); updateDisplay(); // 更新显示初始状态空 }loop()函数是永不停止的主循环实现了状态机的逻辑。void loop() { char key readKeypad(); // 1. 读取按键 if (key ! \0) { // 2. 如果有按键被按下 Serial.print(Key Pressed: ); // 调试信息强烈建议保留 Serial.println(key); // 3. 根据按键类型进行分发处理 if (key 0 key 9) { // 数字键 handleNumber(key - 0); // 将字符0转换为数字0 } else if (key .) { // 小数点键 handleDecimal(); } else if (key C) { // 清除键 handleClear(); } else if (key || key - || key * || key /) { // 运算符键 handleOperator(key); } else if (key ) { // 等号键在我们的映射里可能是#或其他 handleEquals(); } // 每次按键处理后都更新屏幕显示 updateDisplay(); // 处理完一次按键后加一个短暂延时防止在用户按住键时过快处理 delay(200); } // 如果没有按键就快速循环继续检测 }这个loop()的结构非常清晰读取 - 判断 - 分发处理 - 更新UI。这是嵌入式事件处理的标准范式。3.4 关键功能函数实现现在来看几个核心的处理函数它们直接体现了计算器的逻辑。处理数字键输入void handleNumber(int num) { if (isFirstOperand) { // 正在输入第一个数 if (!hasDecimal) { operand1 operand1 * 10 num; // 整数部分左移相加 } else { // 小数部分 operand1 operand1 num * decimalPlace; decimalPlace / 10; // 下一位小数位权重 } } else { // 正在输入第二个数 if (!hasDecimal) { operand2 operand2 * 10 num; } else { operand2 operand2 num * decimalPlace; decimalPlace / 10; } } // 将当前操作数转换为字符串用于显示 if (isFirstOperand) { displayString String(operand1, 4); // 显示4位小数 // 去除末尾无意义的零 while (displayString.endsWith(0)) { displayString.remove(displayString.length() - 1); } if (displayString.endsWith(.)) { displayString.remove(displayString.length() - 1); } } // 第二个数的显示更新在updateDisplay()中统一处理 }注意这里使用String(operand1, 4)来保留4位小数但String对象在内存有限的微控制器上频繁创建和销毁可能引起内存碎片。对于更严谨的项目可以考虑使用dtostrf()函数将浮点数格式化为字符数组。处理运算符void handleOperator(char op) { // 如果已经有一个运算符但第二个操作数还没输入即连续按运算符则用新运算符替换旧的 // 这是一种简单的用户体验优化 if (!isFirstOperand operand2 0) { operation op; displayString String(operand1) String(op); return; } // 如果这是第一次按运算符切换状态到“输入第二个数” if (isFirstOperand) { isFirstOperand false; operation op; hasDecimal false; // 为新操作数重置小数点标志 decimalPlace 0.1; displayString String(operand1) String(op); } else { // 如果已经有一个运算符和第二个数用户又按了新运算符则先计算当前表达式 // 这实现了“从左到右”的连续计算例如1 2 * 3 会先算 123再算 3*39 handleEquals(); // 先计算当前结果 operand1 operand2; // 结果成为新的第一个操作数 operand2 0; // 清零第二个操作数 operation op; // 设置新的运算符 displayString String(operand1) String(op); } }这里实现了原文提到的“从左到右计算忽略运算优先级”。1 2 * 3会得到9而不是7。如果你想实现标准的优先级需要引入表达式解析如调度场算法复杂度会大大增加。处理等号和清除void handleEquals() { if (operation \0 || (isFirstOperand operand2 0)) { return; // 没有有效的运算可执行 } float result; switch (operation) { case : result operand1 operand2; break; case -: result operand1 - operand2; break; case *: result operand1 * operand2; break; case /: if (operand2 ! 0) { result operand1 / operand2; } else { displayString Error:Div by 0; updateDisplay(); delay(2000); handleClear(); return; } break; default: return; } // 显示结果并重置状态准备下一次计算 displayString String(result, 6); // 显示结果保留6位小数 // 清理末尾的零 while (displayString.endsWith(0)) { displayString.remove(displayString.length() - 1); } if (displayString.endsWith(.)) { displayString.remove(displayString.length() - 1); } operand1 result; // 结果可以作为下一次计算的第一个操作数 operand2 0; operation \0; isFirstOperand true; // 注意这里重置为true意味着下次输入数字会覆盖结果。有些计算器设计是false让用户可以继续对结果运算。 hasDecimal false; decimalPlace 0.1; } void handleClear() { // 完全重置计算器状态 operand1 0; operand2 0; operation \0; isFirstOperand true; hasDecimal false; decimalPlace 0.1; displayString 0; }关于除零错误在嵌入式系统中进行除法运算前检查除数是否为零是必须的。直接除以零会导致程序跑飞或产生非法值如inf。这里我们选择在屏幕上显示错误信息然后自动清除这是一种友好的处理方式。更新显示函数void updateDisplay() { lcd.clear(); lcd.setCursor(0, 0); // 第一行显示当前完整的表达式或状态 String topLine ; if (operation ! \0 !isFirstOperand) { topLine String(operand1, 2) String(operation) String(operand2, 2); } else { topLine displayString; } // 确保显示内容不超过16列 if (topLine.length() 16) { topLine topLine.substring(topLine.length() - 16); } lcd.print(topLine); lcd.setCursor(0, 1); // 第二行通常显示当前输入的数字或结果预览这里简单显示提示符 if (isFirstOperand) { lcd.print(Input 1st Num); } else { lcd.print(Input 2nd Num); } }显示逻辑可以根据你的喜好调整比如第二行可以实时显示当前输入的数字。4. 关键实操步骤与深度调试技巧有了代码接下来就是让它跑起来。这个过程会遇到几个典型的坑我结合自己的经验给你捋清楚。4.1 步骤一I2C LCD地址扫描这是必须做的第一步否则屏幕一片漆黑。上传以下代码到Arduino#include Wire.h void setup() { Wire.begin(); Serial.begin(9600); Serial.println(I2C Scanner is starting...); } void loop() { byte error, address; int nDevices 0; Serial.println(Scanning...); for(address 1; address 127; address ) { Wire.beginTransmission(address); error Wire.endTransmission(); if (error 0) { Serial.print(I2C device found at address 0x); if (address16) Serial.print(0); Serial.print(address, HEX); Serial.println( !); nDevices; } else if (error4) { Serial.print(Unknown error at address 0x); if (address16) Serial.print(0); Serial.println(address, HEX); } } if (nDevices 0) { Serial.println(No I2C devices found. Check wiring!); } else { Serial.println(Scan complete.); } delay(5000); // 每5秒扫描一次 }打开串口监视器波特率9600你会看到类似I2C device found at address 0x27的输出。记下这个十六进制数它就是你的LCD_ADDR。4.2 步骤二One Pin Keypad阈值校准这是本项目最核心也最容易出错的环节。你需要获取专属于你那套硬件的16个阈值。上传校准程序编写一个简单的程序循环读取analogRead(KEYPAD_PIN)的值并打印到串口监视器。void setup() { Serial.begin(9600); } void loop() { int val analogRead(A0); // 假设接在A0 Serial.println(val); delay(100); // 每100ms读一次 }物理按键与顺序映射给你的4x4键盘的16个键编号0-15。你可以用贴纸临时标记。确定一个固定的、你不会弄乱的顺序比如从左到右、从上到下。记录数据打开串口监视器依次按下每一个键并保持按住观察串口输出的数值。它会稳定在一个值附近小幅波动。记录下这个稳定的中心值。例如按下“7”键读数稳定在245附近那么myThresholds[0] 245假设“7”键对应数组索引0。填充数组将16个键对应的稳定值按你定义的顺序填入代码中的myThresholds数组。验证校准写一个测试程序调用readKeypad()函数并在串口打印按下的键字符。依次按下所有键确保每个键都能被正确识别且没有串键按A出B或失灵的情况。避坑指南电源稳定性校准和运行时尽量使用同一电源比如都连接电脑USB。不同的电源尤其是有些劣质充电器带来的噪声不同可能导致校准值失效。接触不良确保所有杜邦线插接牢固面包板接触点良好。松动的连接会导致ADC读数飘忽不定。阈值容限如果某个键偶尔识别不到可以适当增大readKeypad()函数中的误差容限比如从20调到25。如果出现串键说明两个键的阈值太接近了可能需要更精细的校准或者检查电阻网络是否有问题。4.3 步骤三代码集成与上传库安装在Arduino IDE中点击“工具” - “管理库”搜索“LiquidCrystal I2C”找到由Frank de Brabander开发的版本进行安装。整合代码将前面章节的所有代码片段整合到一个.ino文件中。确保myThresholds数组已替换为你自己的值LCD_ADDR已修改正确keyMap数组的映射符合你的键盘布局。选择板卡与端口在“工具”菜单中正确选择你的Arduino型号如Arduino Uno和对应的COM端口。编译与上传点击“验证”对勾图标检查代码错误。无误后点击“上传”右箭头图标。观察结果上传完成后计算器应该就能工作了。尝试进行一些计算如3.14 2.7。4.4 步骤四系统化调试与问题排查即使按照步骤做也可能会遇到问题。下面是一个系统化的排查清单现象可能原因排查步骤LCD屏幕不亮/无显示1. 电源接反或未接通。2. I2C地址错误。3. 对比度电位器未调好。4. 背光未开启代码缺少lcd.backlight()。1. 用万用表检查VCC和GND间电压是否为5V。2. 运行I2C扫描程序确认地址。3. 缓慢旋转对比度电位器。4. 检查代码中是否调用了lcd.backlight()。LCD显示乱码1. 初始化顺序或参数错误。2. 通信受到干扰。1. 确保lcd.init()在setup()中最早被调用之一。2. 检查I2C线SDA, SCL是否远离电源等噪声源线是否过长。按键无反应1. Keypad信号线未接或接错。2. 阈值数组myThresholds完全错误。3. 误差容限abs(...) 20设置过小。4. 按键消抖delay(50)时间过长导致响应迟钝。1. 运行校准程序看串口是否有数值变化无按键时一个值按下时明显变化。2. 重新校准阈值。3. 增大误差容限如改为30。4. 减少消抖延时如改为20ms。按键识别错误串键1. 两个按键的阈值太接近。2. 电源噪声导致ADC读数不稳定。1. 重新校准确保每个键的阈值至少有30-50的差距。如果硬件上就接近可能需要修改keyMap顺序或调整误差容限。2. 在Arduino的5V和GND之间并联一个100uF的电解电容稳定电源。计算结果显示错误1. 运算符处理逻辑错误。2. 浮点数精度问题如0.10.2不等于0.3。3. 状态机变量如isFirstOperand在某个分支未正确更新。1. 使用串口打印调试在handleOperator和handleEquals中加入Serial.println输出操作数和运算符跟踪逻辑流。2. 对于嵌入式计算浮点数精度误差是正常的。显示时可以用String(value, 4)限制小数位数或者考虑使用整数运算如以分为单位。3. 仔细检查所有改变状态的地方特别是handleEquals()和handleClear()。程序运行一段时间后死机1. 内存泄漏频繁创建String对象。2. 数组越界访问。1. 尽量减少在loop()中创建新的String对象。考虑使用全局字符数组char array和snprintf进行格式化。2. 检查所有数组如myThresholds,keyMap的访问索引是否可能超出0-15。一个高级调试技巧串口打印状态机在loop()的开头或每个状态处理函数里打印关键变量的值这是理解程序运行状态的“上帝视角”。void debugPrintState() { Serial.print(op1:); Serial.print(operand1); Serial.print( op2:); Serial.print(operand2); Serial.print( isFirstOp:); Serial.print(isFirstOperand); Serial.print( hasDecimal:); Serial.print(hasDecimal); Serial.print( display:); Serial.println(displayString); }在关键节点调用这个函数通过串口监视器观察变量如何变化能快速定位逻辑错误。5. 项目优化与扩展思路一个基础版本的计算器做出来了但这只是开始。你可以把它当作一个平台尝试加入更多功能这能让你学到更多。5.1 功能优化方向支持负数输入增加一个“/-”按键。其逻辑是当处于输入数字状态时按下此键将当前操作数operand1或operand2乘以-1。需要在handleNumber等函数中考虑负数的显示和计算。实现运算优先级挑战性这需要改变“输入即计算”的范式。可以引入一个表达式缓冲区。用户输入1 2 * 3程序先将其存储为字符串或一个结构体数组[1, , 2, *, 3]。当按下“”时调用一个表达式求值函数使用“调度场算法Shunting-yard algorithm”将中缀表达式转换为后缀表达式逆波兰表示法然后再求值。这是编译器设计和计算器算法中的经典题目。添加历史记录功能由于Arduino内存有限可以只保存最近一次或几次的计算表达式和结果。使用一个数组或结构体来存储历史记录并通过增加“上翻/下翻”按键来查看。改善UI/UX让第二行LCD显示当前输入的数字第一行显示完整的表达式。添加按键声音反馈用一个无源蜂鸣器。实现长按“C”键清除所有目前是清除当前输入短按“C”键退格删除一位。5.2 硬件扩展方向制作外壳用亚克力板、3D打印或者甚至一个旧的计算器外壳给你的作品一个“家”。这涉及到简单的结构设计。改为电池供电使用一块9V电池配合一个5V稳压模块如LM7805或者直接用3.7V锂电池配合升压模块制作一个便携版本。注意电池的低电量管理和自动关机功能。更换显示模块尝试使用OLED屏幕I2C接口同样适用它对比度高、更省电、显示效果更现代。更换输入设备尝试使用触摸屏或电阻触摸板来代替矩阵键盘学习另一种输入方式的编程。5.3 代码结构优化对于想深入嵌入式编程的朋友可以重构代码使其更模块化、更易于维护将键盘驱动抽象为类创建一个Keypad类将阈值校准、按键读取、消抖逻辑封装进去。主程序只调用keypad.getKey()。将计算逻辑抽象为类创建一个CalculatorEngine类专门负责处理操作数、运算符和计算与显示和输入完全解耦。使用状态机库对于复杂的状态转换可以使用像FiniteStateMachine这样的库来管理使逻辑更清晰。这个基于Arduino的计算器项目就像一把钥匙帮你打开了嵌入式系统开发的大门。从硬件连接到软件调试从基础逻辑到状态机设计你走过的每一步都是未来更复杂项目的基石。我最深的体会是嵌入式开发中耐心和系统化的调试方法比写出华丽的代码更重要。那个让你折腾半天的按键阈值校准教会你的是硬件世界的“不精确性”和如何用软件去适应它那个除零错误的处理提醒你边界条件检查的重要性。