算术运算的C程序实现

官方信息技术老师·10 页·深入(追求细节与边界)·0 次浏览·3 天前
运算符优先级类型转换顺序结构边界情况

算术运算的C程序实现

看清运算符优先级、类型转换与边界情况的每一步

按 空格/→ 演示下一步

1 / 10 页

全部页面点击任意一页,跳回舞台从这页播放

运算符优先级类型转换顺序结构边界情况

算术运算的C程序实现

看清运算符优先级、类型转换与边界情况的每一步

1第 1 页 · 算术运算的C程序实现

C语言算术表达式概念

上一页我们看到 a+b、x*3 这些运算在程序中跑起来——它们在 C 语言里都属于「算术表达式」。那么,符合什么规则的写法才算合法的算术表达式?

表达式定义
操作数与运算符按语法规则组合、最终求出一个值的代码片段
操作数来源
常量、变量、函数调用,甚至另一个表达式(允许嵌套)
运算符分类
一元(取负、自增)和二元(加减乘除取模)
求值顺序
优先级决定先算什么,结合性决定同优先级从左还是从右
隐式类型转换
char/short 自动升为 int;混合类型按「向更宽类型靠拢」规则转换
数学试卷上的算式对应 →C 算术表达式

数学式只在纸上算;C 表达式会被编译求值,还可能改变变量(副作用)

2第 2 页 · C语言算术表达式概念

算术运算的实现

上一页我们把表达式拆成「操作数 + 运算符」。但 + 号让两个数变成一个,并不简单——背后涉及运算符种类、类型提升、整数与浮点的不同路径,还有溢出这类边界行为。

五类算术运算符
加+ 减- 乘* 除/ 取模%;% 只对整数有效,整数除 / 直接截断小数
隐式类型提升
低精度向高精度靠拢:char/short→int→long→float→double
整数浮点分路
CPU 对两者走不同指令通路,语法相似但底层机制完全不同
溢出的两种命运
有符号溢出是未定义行为 UB,无符号按 2ⁿ 环绕——同句代码不同命
不同币种相加先换汇对应 →混合类型的隐式提升

人民币+美元不能直加,得先按汇率统一;类型提升就是 C 语言自动做的「换汇」

unsigned(a+b)(a+b)(mod2n)\text{unsigned}(a+b) \equiv (a+b) \pmod{2^n}
3第 3 页 · 算术运算的实现

整数相除

你口袋里有 10 块钱,分给 3 个朋友——每人拿 3 块,剩的 1 块你装回去。C 语言的整数相除就是这样:只留整数,零头不补。

定义
两个整数相除,结果只保留整数部分,小数部分直接丢弃
截断方向
C99 起向零截断:7/2==3、(-7)/2==-3;C89 中负数行为由实现定义
类型决定行为
任一操作数为浮点就升级为浮点除法;整数后缀 u/l 只影响宽度
边界陷阱
除数为零是未定义行为;INT_MIN/-1 在有符号整数上溢出
分钱时的取整对应 →整数相除的截断

10 分 3 人各拿 3,零头不找零——结果就是 3,不是 3.33

7/2=3,(7)/2=3 (向零截断)7/2=3,\quad (-7)/2=-3\text{ (向零截断)}
4第 4 页 · 整数相除

输入格式造成的计算错误

上一页的整数相除会丢小数。但更隐蔽的 bug 来自输入环节:scanf 的格式说明符和实际输入不一致时,程序不会报错,却悄悄算错。

格式符是读取契约
scanf 按 %d、%f 等格式符从输入流中匹配字符,能匹配就读走,不能匹配就停下
整数符遇小数 → 截断
输入 3.14 给 %d 时,只读走整数部分 3,".14" 留在缓冲区里没被消化
残留污染后续读取
下一次 scanf("%d") 看到 ".14" 开头,无法解析为整数,读取失败
程序不报错但结果错
scanf 失败后变量可能保持原值,编译运行都正常,计算结果却已偏离预期
点菜报菜号对应 →scanf 按 %d 读整数

你喊"3号"服务员记 3;喊"3.14号"她只听到"3号"记 3,".14" 卡在下单系统里影响下一单

5第 5 页 · 输入格式造成的计算错误

求余运算

上一页讲整数相除只拿到商,但除法其实有两件事:分掉的部分和剩下的部分。剩下的那一块,就是这一页要讲的求余运算。

求余的定义
a % b 是 a 除以 b 后剩下的部分,等价于 a − (a/b)*b
操作数必须为整数
% 两边必须都是整数;浮点取余要用 fmod(),否则编译报错
结果符号看被除数
C99 起结果符号与左边一致:−7 % 3 = −1,而非 1
典型应用场景
判奇偶(n%2)、判整除(n%k)、取个位(n%10)、周期取模(hash%n)
分糖果给小朋友对应 →求余运算

7颗糖分给3人,每人2颗分完,还剩1颗——剩下的就是7%3的结果

a=b×a/b+(amodb),0amodb<ba = b \times \lfloor a/b \rfloor + (a \bmod b),\quad 0 \le a \bmod b < |b|
6第 6 页 · 求余运算

自增自减运算

在 for 循环里我们写过 i++,它每次让 i 加 1。但 ++ 放在 i 前面和后面,行为其实不一样——尤其当它出现在更复杂的表达式里时。

前缀 ++i / --i
先把变量加/减 1,再把新值交给外层表达式使用
后缀 i++ / i--
先把变量的当前值交给外层表达式,再加/减 1
副作用与求值点
既读又写变量;同一表达式多次自增同一变量结果未定义
典型应用场景
循环计数、序列号生成、指针步进(如 p++ 移到下一元素)
翻书读页对应 →前缀 vs 后缀自增

++i 像先翻到新页再读;i++ 像先读完当前页再翻页

r=++i得到新值r=i++得到旧值r = ++i \text{得到新值} \quad r = i++ \text{得到旧值}
7第 7 页 · 自增自减运算

复合运算

自增自减处理的是 a±1 的情形。日常更频繁的是 a = a + 5 这种「读旧值、加一下、写回」三步——C 把它压成一个运算符 +=,这就是复合运算。

复合赋值运算符
把运算与赋值合一的简写:+=、-=、*=、/=、%=
等价但不等同
a += b 与 a = a + b 含义同,但左值 a 只求值一次
表达式有返回值
复合运算是表达式,结果是赋值后的新值,可继续串联
隐式类型转换
右值按左值类型截断,short += 1.5 后仍是 short
求值顺序未定义
a[i++] += i 这类表达式中,i 何时取值没有规定
便签上改写数字对应 →a += b

擦掉旧数写上新数,比另起一行写 a=a+b 更直接

aop=ba=a op ba \mathbin{op}= b \equiv a = a \text{ op } b
8第 8 页 · 复合运算

本节要点

  • 类型决定行为:整数除法直接截断小数,求余只接受整数
  • i++ 返回旧值、++i 返回新值,混用于同一表达式是隐患
  • scanf 格式串里的空白字符会改变输入解析边界
  • 复合赋值是语法糖,不改变语义也不带来性能提升
  • 混合类型运算会触发隐式转换,结果常常反直觉
延伸主题:运算符优先级与结合性显式强制类型转换表达式求值的未定义行为
9第 9 页 · 本节要点

课后思考

先自己琢磨,再看参考答案。三个问题分别检验核心理解、应用思路与边界认知。

1为什么C语言中整数相除会自动截断小数部分,而不是像数学那样给出精确分数?这种设计会带来什么实际影响?

参考答案C语言追求执行效率,整数除法用CPU原生指令直接截断,比浮点除法快得多;实际影响是新人常在平均分计算等场景里踩坑。

2如果需要计算1÷3并保留两位小数,在C语言中有哪些实现方式?它们各自有什么局限?

参考答案方法一:把操作数强转为double再除,简单但有浮点精度误差;方法二:分子分母同放大100倍后整除再回退,精确但代码繁琐,没有银弹。

3多个自增自减运算符出现在同一个表达式中(比如`i++ + ++i`),C语言能保证结果可预测吗?为什么?

参考答案不能保证。C标准把「同一变量在表达式中被多次修改且无序列点分隔」列为未定义行为,编译器可以给出任何结果,代码应避免这种写法。

10第 10 页 · 课后思考