零花钱引发的“血案”:为什么 7÷2 不是 3.5?
你有没有遇到过这种情况?明明数学学得好好的,一进编程的世界,连最基础的加减乘除都开始“背叛”你。比如,你让 C++ 算 7 / 2,它居然理直气壮地告诉你答案是 3。你问它是不是搞错了?它只会冷冷地回答:“我是整数除法,小数部分?扔了。”
这就像你拿着10块钱去买3块钱的奶茶,找零7块,可老板只给你7张皱巴巴的一块钱,还跟你说“7就是7,不存在什么7.0”。你愣在原地,忽然意识到:数学课上的除法,和编程里的除法,根本不是同一个物种。
先认识一下这群“小工具”
C++ 里的算术运算符,本质上就是数学课上那套加减乘除的“代码版翻译”:
+加法:2 + 3→ 5-减法:5 - 2→ 3*乘法:3 * 4→ 12(注意乘号是星号,不是×)/除法:10 / 2→ 5(斜杠,不是÷)%取余数:7 % 3→ 1(读作“7模3”,结果是7除以3的余数)
前三个没什么好说的,几乎和数学一模一样。真正的“坑”都藏在除法和取余数里。
整数除法:它是个“只进不舍”的混蛋
对不起,说“只进不舍”不准确,它其实是个“直接砍掉”的屠夫。
在 C++ 里,两个整数相除,结果还是整数。7 / 2 数学上是3.5,但它会毫不犹豫地变成3。为什么?因为两边都是 int,C++ 觉得“既然是整数,结果就得是整数”,于是把小数部分一刀切掉。
这个特性在生活里其实很合理:7个苹果分给2个小朋友,每人完整的苹果是3个,剩下1个。你不可能给每人3.5个苹果——除非你愿意把苹果切开。
所以当你写:
int a = 7;
int b = 2;
int c = a / b; // c = 3
别惊讶。想要得到小数结果,必须让至少一个数“看起来像小数”:
double d = 7.0 / 2; // 3.5
double e = 7 / 2.0; // 3.5
double f = (double)a / b; // 3.5
这就是为什么在很多程序里,你看到有人写 1.0 * 某整数——他是在强制“转生”成小数。
取余数:专门研究“还剩多少”
取余数 % 是编程独有的“好朋友”。它不关心商是多少,只关心余数。
你一共有10块饼干,要平均分给3个朋友。每人3块,还剩几块?10 % 3 答案是1。
这个运算符在算法题里简直就是“万能钥匙”。判断奇偶用 n % 2,判断闰年用 n % 4、n % 100、n % 400,处理循环节用 n % 周期……几乎无处不在。
但有个铁律:% 只能用于整数。你写 7.5 % 3,编译器会直接甩脸子给你看。
一道“送分题”背后的思维转变
来看一道经典的入门题:给你一个三位数 x,求它各位数字之和。比如 123,输出 1+2+3=6。
这道题考的是什么?就是除法 / 和取余 % 的组合使用。
你怎么从一个三位数里把百位、十位、个位分别抠出来?
- 百位:
x / 100。123除以100,整数除法得到1,完美。 - 十位:
x / 10 % 10。123除以10得12(整数除法),再取余10得到2。先“砍掉”个位,再“取”出十位。 - 个位:
x % 10。123除以10余3。
所以核心代码就三行:
int a = x / 100; // 百位
int b = x / 10 % 10; // 十位
int c = x % 10; // 个位
int sum = a + b + c;
你看,这道题漂亮地展示了 / 和 % 的分工:/ 用来“向右移动数位”,% 用来“截取末尾数位”。一旦理解了这层关系,以后处理任意数位的拆分,你都游刃有余。
优先级:别让数学课代表教坏了你
C++ 的运算顺序和数学一致:先乘除、后加减,括号最优先。这本来没什么,可新手总会栽在“想当然”上。
比如你想算“5加上3,再乘以2”,如果写成:
int result = 5 + 3 * 2;
根据优先级,先算 3*2=6,再加5,得11。而不是你想要的16。
这种错误很隐蔽,尤其是当表达式变长时。我的建议是:别跟优先级较劲,有歧义就加括号。(5 + 3) * 2 谁看都知道你干什么。
另一道题正好把这优先级玩明白了:问 20 - 10 / 2 等于多少?答案是15,因为先算 10/2=5,再算 20-5=15。如果你从左往右算,就会得 (20-10)/2=5——丢了10分。这种题不是考计算,而是在考你“有没有把优先级刻在骨子里”。
真正的“大魔王”:一道机器人走边框的题
前面那些都是热身,真正能同时用上加减乘除和取余的,是这道题:
19×19的围棋棋盘,机器人只能沿着最外圈边框移动。给定边框上两个点
(x1,y1)和(x2,y2),求最少走多少步。
乍一看像是最短路径问题,但仔细想想:机器人只能在矩形边框上走,那路径只有两种可能——顺时针,或者逆时针。我们要做的,就是分别算出这两种走法的距离,取较小的那个。
怎么算?这里有一个非常漂亮的技巧:把“环形”变成“直线”。
想象你把这个矩形边框从某个角落剪开,拉成一条线。给每个边框上的点一个“位置编号”,表示它距离起点的步数。比如我从 (1,1) 出发,顺时针走:
- 底边 (
x=1):(1,1)位置0,(1,2)位置1,……,(1,19)位置18。 - 右边 (
y=19):(2,19)位置19,……,(19,19)位置36。 - 顶边 (
x=19):(19,18)位置37,……,(19,1)位置54。 - 左边 (
y=1):(18,1)位置55,……,(2,1)位置71,(1,1)位置72(也就是0)。
这样每个点都有一个“一维坐标”pos。那么两点之间的步数,就是它们在环上的距离:min(|pos1 - pos2|, 72 - |pos1 - pos2|)。为什么是72?因为整个边框周长是 (19-1)*4 = 72。走短的那条弧,就是最少步数。
计算 pos 需要分情况讨论,正好用上算术运算符:
int pos(int x, int y) {
if (x == 1) return y - 1; // 底边
if (y == 19) return 18 + x - 1; // 右边
if (x == 19) return 36 + (19 - y); // 顶边
return 54 + (19 - x); // 左边
}
然后主程序只需要:
int p1 = pos(x1, y1);
int p2 = pos(x2, y2);
int ans = min(abs(p1 - p2), 72 - abs(p1 - p2));
样例里 (6,1) 到 (10,19):(6,1) 在左边,位置是 54 + (19-6)=67;(10,19) 在右边,位置是 18+10-1=27。差值40,周长72,短距离就是 72-40=32。完全符合。
这道题的精髓,就是用一个巧妙的映射,把“在环上走”的问题转化成“在直线上走”的问题。而整个转化过程中,+、-、*、%、绝对值这些基础运算符像乐高积木一样拼在一起,解决了看似复杂的问题。
最后,给初学者的几个“保命”原则
- 两个整数相除结果还是整数,想要小数就让至少一个变成
double。 %只爱整数,别拿小数去碰它。- 不确定优先级?用括号把你能确定的部分包起来。
- 别用中文符号写代码,
-、×、÷在编译器眼里就是外星文。 - 永远不要除以0,不然程序会当场“去世”。
算术运算符是编程里最基础的工具,但基础不代表简单。真正的高手,能在这些简单运算中组合出优雅的解法,就像用24个字母写出伟大的小说。
接下来,你可以去研究一下 ++、-- 自增自减,或者 +=、*= 这类复合赋值运算符。它们能让代码更简洁,但也藏着更多“先取值还是先自增”的陷阱。等你把这些都吃透,就离写出漂亮代码更近一步了。
关于作者
我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。
这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。
如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)