news 2026/10/21 8:35:37

C++算术运算避坑指南:整数除法、取模与赋值陷阱

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++算术运算避坑指南:整数除法、取模与赋值陷阱

1. 从算术运算开始:C++ 那套"看着简单"的加减乘除

说实话,第二天的内容往往比第一天更容易让人栽跟头。第一天大家还在兴奋地折腾变量声明、cout 输出,觉得"C++ 也就这样嘛"。到了基本运算这一章,表面上全是小学一年级就会的加减乘除,可真到自己写代码的时候,各种莫名其妙的输出结果、莫名其妙的编译警告,立刻让人怀疑人生。

我把这天的学习笔记重新整理了一份,从算术运算入手,逐个把运算符的脾气摸清楚。别嫌啰嗦,这些看起来人畜无害的符号,恰恰是后面写算法、写工程代码时最容易埋雷的地方。

算术运算符一共就六个:加(+)、减(-)、乘(*)、除(/)、取模(%)、自增自减(++ / --)。其中前四个在所有编程语言里都差不多,真正需要花心思的是除法和取模,还有自增自减的前置后缀区别。

先看一个最经典的坑——整数除法。C++ 里,两个整数相除,结果直接丢掉小数部分,而不是四舍五入。比如 7 / 2 的结果是 3,不是 3.5。这可能和很多人的直觉不符,但语言就是这么规定的:整数除以整数,结果永远是一个整数。如果你想要 3.5,就必须让至少一边变成浮点数,比如 7.0 / 2,或者 7 / 2.0。

注意:C++ 的整数除法并非严格的"向下取整",而是"向零取整"。这一点在涉及负数时特别容易出问题,我后面专门讲。

取模运算符 % 是取余数,也就是整数除法除完之后剩下的那点"零头"。7 % 3 的结果是 1,因为 7 除以 3 得 2 余 1。这个操作在判断奇偶、循环数组下标、控制输出换行等场景里极其常用。比如判断一个数是不是偶数,直接写num % 2 == 0就行,这比任何其他办法都干净利落。

但取模有个非常容易忽略的限制:在 C++ 标准里,% 的两个操作数必须是整数类型。你不能写7.5 % 2——浮点数做取模是不合法的,编译器直接报错。很多人第一次在这个地方被卡住,还以为是写法问题,其实是类型不对。

1.1 整数除法遇上负数:向零取整的"反直觉"

我之所以单独把负数除法拎出来说,是因为它藏得特别深。很多人做了一辈子开发,可能都没认真想过 -7 / 2 到底等于多少。我当年也以为答案是 -3.5,既然是整数除法,取整后应该是 -3 或者 -4,具体是哪个其实取决于语言。

C++ 的规则是:商向零取整。-7 / 2 的结果是 -3,因为 -3.5 向零取整就是 -3。同理,7 / -2 还是 -3。那么余数呢?C++ 规定:余数的符号与被除数保持一致(或者说,结果满足(a / b) * b + a % b == a这个恒等式)。于是 -7 % 2 的结果是 -1,7 % -2 的结果是 1。

这里我建议你拿笔算一下验证,因为负数余数的规则在不同语言里并不完全一样,C 系语言基本都照这个规矩来。实际项目里如果遇到负数取模的场景,最好提前想清楚是"向零取余"还是"往负无穷取余",否则边界条件很容易算错。

1.2 自增自减:前置和后缀不是一回事

自增自减是 C++ 里最有特点的一对运算符。++i是先加再用,i++是先用再加。单独写一行时两者没区别,一旦塞进表达式里,差别立刻显现。

int a = 5; int b = a++; // b 得到 5,之后 a 变成 6 int c = ++a; // a 先变成 7,然后 c 得到 7

很多人背过这个规则,但写代码时还是会搞混。我的建议是:如果不是必须,不要把自增自减写在复杂表达式里。像arr[i++] = x;这种写法虽然简洁,但读代码的人很容易在脑子里多转三圈。它们的存在是为了在循环里写i++方便,不是为了让你玩一元运算符大杂烩。

另外一个隐藏细节是:自增自减本质上是一个有副作用的操作,它在修改变量本身的值。如果你对同一个变量在一个表达式里既自增又取值,比如int x = i++ + ++i;,这是未定义行为(Undefined Behavior)。不同的编译器、不同的优化级别,算出来的结果都可能不一样。这种代码在任何正经项目里都不该出现。

2. 赋值运算与复合赋值:那些"顺手"写的坑

从基本运算的角度看,赋值运算符似乎没什么好讲的,一个等号而已。但往深了看,赋值这一块牵扯到 C++ 非常多核心机制:左值右值、类型转换、连续赋值、复合赋值运算符的效率等。不少"看着对"的代码,其实在赋值环节就歪了。

首先是复合赋值运算符,就是把运算和赋值合在一起:+=、-=、*=、/=、%=,以及后面会讲的位运算复合版本&=、|=、^=、<<=、>>=。它们的含义很直观:a += 3等价于a = a + 3。

不过如果只是把它理解为简写,那就低估了它的价值。复合赋值在 C++ 里还有一个隐性的优势:它只对左值做一次求值。拿数组下标来说,arr[get_index()] += 5里的get_index()只调用一次;而如果写成arr[get_index()] = arr[get_index()] + 5,函数会被调用两次。如果这个函数有副作用或者代价高昂,那差距就大了。所以我在项目里一直要求同事能用复合赋值就不要展开写,不是因为少打几个字,而是因为语义更精确。

2.1 连续赋值与括号的阅读习惯

C++ 支持a = b = c = 10;这种连续赋值,含义是从右往左依次赋值。语法上合法,也符合结合性规则。但实际工程里,我强烈不建议写成这样。

原因不在于正确性,而在于可读性。三个人同时看一行a = b = c = 10;,每个人脑子里解析的顺序可能都不同。你当然可以说"从右往左",但代码审查时这种行总是会引起没必要的讨论。更稳妥的写法是分成三行,或者明确加括号a = (b = (c = 10));——不过说实话,加了括号的连续赋值依然不让人省心。

如果遇到不同类型变量连续赋值,问题更大。int a; double b; a = b = 3.7;,b 先变成 3.7,然后 a 赋值时被截断成 3。如果不留意,很容易把"每句都赋对了值"误写成"全都赋同一个值",类型转换的损耗和精度丢失就这样无声无息地发生了。

2.2 赋值的类型隐式转换:编译通过不等于没毛病

赋值时如果右边类型和左边类型不一致,C++ 会进行隐式转换。这个"隐式"二字最坑人,因为编译器往往连警告都不给。

最典型的是 double 到 int 的窄化转换:int n = 3.99;,n 的结果是 3,小数部分直接丢弃。如果你以为 n 会四舍五入成 4,那就错了。C++ 在这条规则上和很多人的预期不一致,它不进行四舍五入,就是直接截断。

还有 char 到 int、int 到 unsigned 这类转换,更容易引发隐蔽问题。比如 `unsigned int u = 10;

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 8:35:16

基于有限状态机的洗衣机控制器设计与仿真验证

简介&#xff1a;这份《课程设计洗衣机控制器.pdf》面向电子信息、自动化及相关专业的学生与课程设计指导教师&#xff0c;用于完成洗衣机控制器这一典型数字电路课程设计课题。资源为单文件PDF&#xff0c;压缩包约619KB&#xff0c;内容按完整设计流程编排&#xff0c;涵盖课…

作者头像 李华
网站建设 2026/10/21 8:35:14

PHP后门为何能“短小精悍”?从代码原理到应急响应实战

从一次深夜应急响应说起。某天凌晨&#xff0c;同事递过来一份可疑文件&#xff0c;说是客户站点根目录下多了一个PHP文件&#xff0c;体积不到2KB&#xff0c;但站点访问出现异常。打开文件那一刻&#xff0c;看到的不是想象中大段大段看不懂的代码&#xff0c;而是一行极短、…

作者头像 李华
网站建设 2026/10/21 8:35:12

从模糊代号到稳定运行:资源效率分析平台REA落地全记录

前阵子整理团队内部代码仓库&#xff0c;翻到当初一个立项文档&#xff0c;标题栏只有三个小写字母&#xff1a;rea。没有正文、没有关键词、没有验收标准&#xff0c;连使用方都没写清楚。刚开始我以为是"read"拼错了&#xff0c;或者某个第三方库的缩写&#xff0c…

作者头像 李华
网站建设 2026/10/21 8:35:07

MFC单机记忆翻牌游戏:从消息映射到GDI双缓冲的完整实战拆解

简介&#xff1a;这款基于MFC框架开发的单机记忆翻牌游戏&#xff0c;以经典翻牌配对玩法训练玩家记忆力&#xff0c;适合C与Windows桌面程序初学者作为实战练习项目&#xff1b;游戏要求玩家记住卡片位置&#xff0c;在有限的翻牌次数内完成全部配对&#xff0c;既考验短时记忆…

作者头像 李华
网站建设 2026/10/21 8:34:00

本地化AI工作流:为DeepSeek编程智能体注入余额、任务与番茄钟能力

1. 项目概述&#xff1a;当编程智能体不再只是“写代码”&#xff0c;而是你的数字工作台最近在调试一个自动化任务调度系统时&#xff0c;我意识到一个问题&#xff1a;DeepSeek 的编程智能体虽然能写出结构清晰、逻辑严谨的 Python 脚本&#xff0c;但它始终缺了一层“人味”…

作者头像 李华