CC++ & Algorithm

零花钱引发的“血案”:为什么 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 % 4n % 100n % 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(微信同号)

这篇文章对你有帮助吗?

成为第一个评价的人

评论0

还没有评论,来抢沙发~

评论加载中...

想系统学习这个知识点?查看完整知识点 →