第 7 章 变量进阶与数学工具箱
🏗️ 前情回顾:第 6 章你学会了用 C++ 做算术题——加减乘除、取模、自增自减。但真正的程序不能只算加减乘除。绝对值怎么办?平方根怎么办?随机数怎么办?这一章我们打开 C++ 自带的数学工具箱。这些功能 C++ 早就帮你准备好了,拿来就用。同时,我们还会深入变量的更多玩法——类型转换、作用域、const 的高级用法。
🎯 本章目标
学完这一章,你能:
- 正确引入
<cmath>头文件并使用其中的数学函数 - 用
abs、sqrt、pow、max、min完成常见计算 - 用
rand和srand生成随机数 - 知道每个函数的参数和返回值类型
- 学会查阅 C++ 数学函数文档
- 避免常见的函数误用(如
pow的整数误差)
📖 故事引入
假如你被丢到一个荒岛上,只允许带一样工具——大多数人会选瑞士军刀。一把刀,集成了剪刀、螺丝刀、开瓶器、镊子……几十种功能,折叠在一个巴掌大的工具里。
C++ 也有这样一把"瑞士军刀"——它叫 <cmath>(C 数学库)。你不用自己写代码去计算平方根、绝对值、幂运算——这些数学家花了几百年才搞出来的东西,早就被打包好了,你只需要知道"打开哪一层,叫哪个函数"就行。
今天,我们就来打开这把 C++ 数学军刀,一件一件试。
🧱 知识讲解
7.1 打开工具箱:#include <cmath>
在第 2 章你学过了 #include <iostream>——那是输入输出的工具箱。数学函数住在另一个箱子里,叫 <cmath>。
用之前必须先引入:
#include <cmath> // 数学工具箱如果不写这行,下面所有数学函数都会报"未定义"错误。
扩展阅读:
<cmath>的全称是 "C Math",它是从 C 语言继承过来的数学函数库。C++ 也有自己专属的<numeric>等库,但<cmath>是竞赛中最常用的。
7.2 绝对值:abs 和 fabs
绝对值就是"把负号去掉"。-5 的绝对值是 5,3.14 的绝对值还是 3.14。
整数的绝对值用 abs:
#include <cmath>
#include <iostream>
using namespace std;
int main() {
int a = -100, b = 42;
cout << abs(a) << endl; // 100
cout << abs(b) << endl; // 42
cout << abs(a - b) << endl; // |-100 - 42| = 142
return 0;
}小数的绝对值用 fabs:
// ❌ 错误:用 abs 求浮点数绝对值(被截断成整数!)
double x = -3.14;
cout << abs(x) << endl; // 输出 3,不是 3.14!
// ✅ 正确:浮点数用 fabs
double x = -3.14;
cout << fabs(x) << endl; // 3.14口诀:整数
abs,小数fabs。竞赛里整数用得最多,abs是你最常用的。
7.3 平方根:sqrt
sqrt = square root(平方根)。比如 √25 = 5,√2 ≈ 1.414。
cout << sqrt(25) << endl; // 5
cout << sqrt(2) << endl; // 1.41421
cout << sqrt(0) << endl; // 0注意:sqrt 的参数和返回值都是 double 类型。传整数进去,C++ 会自动帮你转。
int len = 9;
double side = sqrt(len); // side = 3.0⚠️ 不能对负数求平方根!
sqrt(-1)会返回nan(not a number,不是数字)。
7.4 幂运算:pow
pow(a, b) = a 的 b 次方($a^b$)。
cout << pow(2, 10) << endl; // 2^10 = 1024
cout << pow(3, 4) << endl; // 3^4 = 81
cout << pow(10, 6) << endl; // 10^6 = 1000000⚠️ 重要提醒:
pow返回的是double类型,存在微小的浮点误差。比如pow(5, 2)可能返回25.00000000000001而不是精确的 25。如果要把结果赋给int,建议加 0.5 再转:cppint result = (int)(pow(5, 2) + 0.5); // 安全的整数做法
7.5 最大值和最小值:max 和 min
从 C++11 开始,max 和 min 定义在 <algorithm> 头文件里。但很多编译器在引入 <cmath> 或 <iostream> 后也能直接用。最简单的做法——如果你只用判断两个数谁大谁小,可以用三目运算符或直接引入 <algorithm>:
#include <algorithm> // max 和 min 的家
#include <iostream>
using namespace std;
int main() {
int a = 15, b = 8;
cout << max(a, b) << endl; // 15
cout << min(a, b) << endl; // 8
double x = 3.14, y = 2.71;
cout << max(x, y) << endl; // 3.14
// 三个数的最大值
cout << max(max(10, 25), 7) << endl; // 25
return 0;
}如果你不想引入
<algorithm>,也可以用a > b ? a : b(第 8 章学过三目运算符)代替max(a, b)。效果一样。
7.6 随机数:rand 和 srand
游戏里敌人随机出现、抽卡随机出牌——这些"随机"都是 rand() 函数生成的。
rand() 返回一个 0 到 RAND_MAX(通常是 32767)之间的随机整数。
#include <cstdlib> // rand 的家
#include <iostream>
using namespace std;
int main() {
cout << rand() << endl; // 比如输出 41
cout << rand() << endl; // 比如输出 18467
cout << rand() << endl; // 比如输出 6334
return 0;
}但是——你把程序关了再开,生成的"随机数"和上一次一模一样!
🎲 因为
rand()不是真随机,它是用一个种子(seed)算出来的"伪随机"。种子不变,序列就不变。
// ❌ 错误:每次运行相同的"随机"数
cout << rand() << endl; // 每次都是 41
cout << rand() << endl; // 每次都是 18467
// ✅ 正确:用时间做种子,每次都不一样
#include <cstdlib>
#include <ctime> // time 的家
#include <iostream>
using namespace std;
int main() {
srand(time(0)); // 用当前时间做种子
cout << rand() % 100 << endl; // 0~99 的随机数
cout << rand() % 6 + 1 << endl; // 1~6 的随机数(骰子)
return 0;
}🎯 生成 a~b 的随机数公式:
rand() % (b - a + 1) + a
举例:生成 1~100:rand() % 100 + 1;生成 60~100:rand() % 41 + 60。
7.7 常用数学函数速查表
| 函数 | 含义 | 示例 | 结果 |
|---|---|---|---|
abs(x) | 整数绝对值 | abs(-7) | 7 |
fabs(x) | 浮点绝对值 | fabs(-3.14) | 3.14 |
sqrt(x) | 平方根 | sqrt(16) | 4.0 |
pow(a,b) | a 的 b 次方 | pow(2, 10) | 1024.0 |
max(a,b) | 较大值 | max(3, 9) | 9 |
min(a,b) | 较小值 | min(3, 9) | 3 |
rand() | 随机整数 | rand() % 10 | 0~9 |
ceil(x) | 向上取整 | ceil(3.1) | 4.0 |
floor(x) | 向下取整 | floor(3.9) | 3.0 |
round(x) | 四舍五入 | round(3.5) | 4.0 |
log(x) | 自然对数 ln(x) | log(2.718) | ≈1.0 |
sin(x) | 正弦(弧度) | sin(3.14159/2) | ≈1.0 |
✋ 动手试试
试试 1:写程序算 $\sqrt{3^2 + 4^2}$(也就是勾股定理:直角边 3 和 4,斜边长多少?)
cout << sqrt(pow(3, 2) + pow(4, 2)) << endl; // 应该输出 5试试 2:生成 10 个 1~100 的随机数,看看它们是不是每次运行都不一样(记得加 srand(time(0))!)。
试试 3:不用 max 和 min,只用三目运算符 ? : 实现找出两个数中的最大值和最小值。
试试 4:把 pow(10, 2) 的结果直接赋给 int,不加 0.5。输出结果。再用 (int)(pow(10, 2) + 0.5) 试试。有区别吗?
⚠️ 你踩过这些坑吗
- [ ] 坑 1:忘了
#include <cmath>— 写了sqrt(25)但没引入头文件→编译错误'sqrt' was not declared。 - [ ] 坑 2:对负数求平方根 —
sqrt(-4)返回nan(不是数字),程序不报错但结果是错的。 - [ ] 坑 3:
rand()直接求范围时忘记 +1 —rand() % 6得到 0~5,不是骰子的 1~6。 - [ ] 坑 4:
pow的浮点误差导致整数差 1 —int result = pow(5, 2);可能得到 24。加 0.5 再截断:(int)(pow(5, 2) + 0.5)。
📝 练习
基础题
1. 选择题
(1)sqrt(49) 的结果是多少? A. 6.0 B. 7.0 C. 49.0 D. 2401.0
(2)pow(2, 5) 的结果是多少? A. 10 B. 16 C. 25 D. 32
(3)abs(-8) 的结果是多少? A. -8 B. 8 C. 0 D. 16
(4)要生成 1~100 的随机数,正确写法是? A. rand() % 100 B. rand() % 99 + 1 C. rand() % 100 + 1 D. rand() % 101
2. 填空题
(1)数学函数住在 ____ 头文件里。
(2)rand() 返回的是 ____ 随机数,需要用 ____ 设置种子。
(3)max(3, max(7, 2)) 的结果是 ____。
(4)fabs 用于求 ____ 类型的绝对值,abs 用于求 ____ 类型的绝对值。
3. 判断题
(1)srand(time(0)) 让每次运行的随机数都不同。( ) (2)sqrt(-4) 会报编译错误。( ) (3)pow(2, 3) 等价于 2 * 2 * 2。( ) (4)floor(3.9) 的结果是 4.0。( )
4. 代码补全
补齐以下代码,让它输出 1~100 之间的一个随机数:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
______(______(0));
int num = ______() ______ 100 ______ 1;
cout << "随机数:" << num << endl;
return 0;
}提高题
5. 分析题
以下代码输出什么?先推算,再上机验证:
cout << floor(3.7) << endl;
cout << ceil(3.1) << endl;
cout << round(3.5) << endl;
cout << round(4.5) << endl;
cout << abs(-10) + sqrt(100) << endl;6. 编程题
输入一个圆的半径 r(double 类型),计算并输出它的周长($2\pi r$)和面积($\pi r^2$)。π 取 3.14159。
7. 编程题
输入两个整数 a 和 b,输出 $\sqrt{a^2 + b^2}$(即直角边为 a 和 b 的直角三角形斜边长)。保留 2 位小数。
挑战题
8. 编程题
写一个"猜数字"游戏:
- 程序用随机数生成一个 1~100 的秘密数字
- 让用户猜,每次提示"猜大了"或"猜小了"
- 直到猜对,输出"恭喜!你猜了 X 次"
9. 编程题
输入一个整数 n,判断它是不是完全平方数(比如 1、4、9、16、25……)。提示:判断 sqrt(n) 是否为整数。
🧠 本章小结
数学工具箱(#include <cmath>):
abs(x) — 整数绝对值
fabs(x) — 小数绝对值
sqrt(x) — 平方根 √x
pow(a, b) — a 的 b 次方(⚠浮点误差)
ceil(x) — 向上取整
floor(x) — 向下取整
round(x) — 四舍五入
比较(#include <algorithm>):
max(a, b) — 较大值
min(a, b) — 较小值
随机(#include <cstdlib>, <ctime>):
srand(time(0)) — 种随机种子
rand() % n + m — 生成 m~(m+n-1) 的随机数📝 配套练习
共8题。核心1题引入cmath,拓展6题覆盖pow/sqrt/ASCII偏移/类型转换,挑战1题全章串联。。★核心(课堂必做) ◆拓展(课后练习) ★★★挑战(选做)
| 级别 | 题号 | 链接 | 覆盖知识点 |
|---|---|---|---|
| ★ 核心 | luogu-B2031 | https://hydro.ac/p/luogu-B2031 | cmath(sqrt)、double、海伦公式 |
| ◆ 拓展 | J0019 | https://hydro.ac/d/srqc/p/J0019 | 复合赋值/=、隐式类型转换 |
| ◆ 拓展 | luogu-B2013 | https://hydro.ac/p/luogu-B2013 | 公式翻译、double、格式化 |
| ◆ 拓展 | luogu-B2014 | https://hydro.ac/p/luogu-B2014 | cmath、π常量、pow |
| ◆ 拓展 | luogu-B2034 | https://hydro.ac/p/luogu-B2034 | pow(2,n)、指数运算、long long |
| ◆ 拓展 | J0013 | https://hydro.ac/d/srqc/p/J0013 | char运算、ASCII偏移 |
| ◆ 拓展 | J0014 | https://hydro.ac/d/srqc/p/J0014 | 立方体、体积公式、幂运算 |
| ★★★ 挑战 | J0026 | https://hydro.ac/d/srqc/p/J0026 | 综合:cin→变量→公式→类型选择→溢出判断 |
💡 练习建议:先完成 2 道★核心题,确保掌握本章基本方法;再完成 6 道◆拓展题,覆盖不同变式和细节;★★★挑战题建议在完成拓展题后再做,涉及多知识点综合。
配套练习
共8题。核心1题引入cmath,拓展6题覆盖pow/sqrt/ASCII偏移/类型转换,挑战1题全章串联。★核心(课堂必做) · ◆拓展(课后练习) · ★★★挑战(选做)
| 级别 | 题号 | 链接 | 覆盖知识点 |
|---|---|---|---|
| ★ 核心 | luogu-B2031 | https://hydro.ac/p/luogu-B2031 | cmath(sqrt)、double、海伦公式 |
| ◆ 拓展 | J0019 | https://hydro.ac/d/srqc/p/J0019 | 复合赋值/=、隐式类型转换 |
| ◆ 拓展 | luogu-B2013 | https://hydro.ac/p/luogu-B2013 | 公式翻译、double、格式化 |
| ◆ 拓展 | luogu-B2014 | https://hydro.ac/p/luogu-B2014 | cmath、π常量、pow |
| ◆ 拓展 | luogu-B2034 | https://hydro.ac/p/luogu-B2034 | pow(2,n)、指数运算、long long |
| ◆ 拓展 | J0013 | https://hydro.ac/d/srqc/p/J0013 | char运算、ASCII偏移 |
| ◆ 拓展 | J0014 | https://hydro.ac/d/srqc/p/J0014 | 立方体、体积公式、幂运算 |
| ★★★ 挑战 | J0026 | https://hydro.ac/d/srqc/p/J0026 | 综合:cin→变量→公式→类型选择→溢出判断 |
练习建议:先在课堂完成 1 道★核心题,掌握本章基本方法;课后完成 6 道◆拓展题,覆盖不同变式和细节;★★★挑战题建议在完成拓展题后再做,涉及多知识点综合。
自查清单:
- [ ] 我知道用哪个头文件引入数学函数
- [ ] 我会用
abs、sqrt、pow进行计算 - [ ] 我知道
max和min怎么用 - [ ] 我能用
rand和srand生成随机数 - [ ] 我知道
pow有浮点误差,整数幂尽量直接乘 - [ ] 我会查速查表找到我需要的函数
- [ ] 我知道
ceil、floor、round的区别
🚀 下章预告
变量会用了,数学函数也会调了——第 1~7 章的知识点散落一地。下一章是 M1 的收官之战:把变量、类型、输入输出、运算、数学函数全部串起来,从零写出一个能用的计算器。准备迎接挑战!