Skip to content

第 7 章 变量进阶与数学工具箱


🏗️ 前情回顾:第 6 章你学会了用 C++ 做算术题——加减乘除、取模、自增自减。但真正的程序不能只算加减乘除。绝对值怎么办?平方根怎么办?随机数怎么办?这一章我们打开 C++ 自带的数学工具箱。这些功能 C++ 早就帮你准备好了,拿来就用。同时,我们还会深入变量的更多玩法——类型转换、作用域、const 的高级用法。


🎯 本章目标

学完这一章,你能:

  • 正确引入 <cmath> 头文件并使用其中的数学函数
  • abssqrtpowmaxmin 完成常见计算
  • randsrand 生成随机数
  • 知道每个函数的参数和返回值类型
  • 学会查阅 C++ 数学函数文档
  • 避免常见的函数误用(如 pow 的整数误差)

📖 故事引入

假如你被丢到一个荒岛上,只允许带一样工具——大多数人会选瑞士军刀。一把刀,集成了剪刀、螺丝刀、开瓶器、镊子……几十种功能,折叠在一个巴掌大的工具里。

C++ 也有这样一把"瑞士军刀"——它叫 <cmath>(C 数学库)。你不用自己写代码去计算平方根、绝对值、幂运算——这些数学家花了几百年才搞出来的东西,早就被打包好了,你只需要知道"打开哪一层,叫哪个函数"就行

今天,我们就来打开这把 C++ 数学军刀,一件一件试。


🧱 知识讲解

7.1 打开工具箱:#include <cmath>

在第 2 章你学过了 #include <iostream>——那是输入输出的工具箱。数学函数住在另一个箱子里,叫 <cmath>

用之前必须先引入:

cpp
#include <cmath>   // 数学工具箱

如果不写这行,下面所有数学函数都会报"未定义"错误。

扩展阅读:<cmath> 的全称是 "C Math",它是从 C 语言继承过来的数学函数库。C++ 也有自己专属的 <numeric> 等库,但 <cmath> 是竞赛中最常用的。

7.2 绝对值:absfabs

绝对值就是"把负号去掉"。-5 的绝对值是 5,3.14 的绝对值还是 3.14。

整数的绝对值用 abs

cpp
#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

cpp
// ❌ 错误:用 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。

cpp
cout << sqrt(25) << endl;   // 5
cout << sqrt(2) << endl;    // 1.41421
cout << sqrt(0) << endl;    // 0

注意:sqrt 的参数和返回值都是 double 类型。传整数进去,C++ 会自动帮你转。

cpp
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$)。

cpp
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 再转:

cpp
int result = (int)(pow(5, 2) + 0.5);  // 安全的整数做法

7.5 最大值和最小值:maxmin

从 C++11 开始,maxmin 定义在 <algorithm> 头文件里。但很多编译器在引入 <cmath><iostream> 后也能直接用。最简单的做法——如果你只用判断两个数谁大谁小,可以用三目运算符或直接引入 <algorithm>

cpp
#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 随机数:randsrand

游戏里敌人随机出现、抽卡随机出牌——这些"随机"都是 rand() 函数生成的。

rand() 返回一个 0 到 RAND_MAX(通常是 32767)之间的随机整数。

cpp
#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)算出来的"伪随机"。种子不变,序列就不变。

cpp
// ❌ 错误:每次运行相同的"随机"数
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() % 100~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,斜边长多少?)

cpp
cout << sqrt(pow(3, 2) + pow(4, 2)) << endl;  // 应该输出 5

试试 2:生成 10 个 1~100 的随机数,看看它们是不是每次运行都不一样(记得加 srand(time(0))!)。

试试 3:不用 maxmin,只用三目运算符 ? : 实现找出两个数中的最大值和最小值。

试试 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() 直接求范围时忘记 +1rand() % 6 得到 0~5,不是骰子的 1~6。
  • [ ] 坑 4:pow 的浮点误差导致整数差 1int 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 之间的一个随机数:

cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
    ______(______(0));
    int num = ______() ______ 100 ______ 1;
    cout << "随机数:" << num << endl;
    return 0;
}

提高题

5. 分析题

以下代码输出什么?先推算,再上机验证:

cpp
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-B2031https://hydro.ac/p/luogu-B2031cmath(sqrt)、double、海伦公式
◆ 拓展J0019https://hydro.ac/d/srqc/p/J0019复合赋值/=、隐式类型转换
◆ 拓展luogu-B2013https://hydro.ac/p/luogu-B2013公式翻译、double、格式化
◆ 拓展luogu-B2014https://hydro.ac/p/luogu-B2014cmath、π常量、pow
◆ 拓展luogu-B2034https://hydro.ac/p/luogu-B2034pow(2,n)、指数运算、long long
◆ 拓展J0013https://hydro.ac/d/srqc/p/J0013char运算、ASCII偏移
◆ 拓展J0014https://hydro.ac/d/srqc/p/J0014立方体、体积公式、幂运算
★★★ 挑战J0026https://hydro.ac/d/srqc/p/J0026综合:cin→变量→公式→类型选择→溢出判断

💡 练习建议:先完成 2 道★核心题,确保掌握本章基本方法;再完成 6 道◆拓展题,覆盖不同变式和细节;★★★挑战题建议在完成拓展题后再做,涉及多知识点综合。


配套练习

共8题。核心1题引入cmath,拓展6题覆盖pow/sqrt/ASCII偏移/类型转换,挑战1题全章串联。★核心(课堂必做) · ◆拓展(课后练习) · ★★★挑战(选做)

级别题号链接覆盖知识点
★ 核心luogu-B2031https://hydro.ac/p/luogu-B2031cmath(sqrt)、double、海伦公式
◆ 拓展J0019https://hydro.ac/d/srqc/p/J0019复合赋值/=、隐式类型转换
◆ 拓展luogu-B2013https://hydro.ac/p/luogu-B2013公式翻译、double、格式化
◆ 拓展luogu-B2014https://hydro.ac/p/luogu-B2014cmath、π常量、pow
◆ 拓展luogu-B2034https://hydro.ac/p/luogu-B2034pow(2,n)、指数运算、long long
◆ 拓展J0013https://hydro.ac/d/srqc/p/J0013char运算、ASCII偏移
◆ 拓展J0014https://hydro.ac/d/srqc/p/J0014立方体、体积公式、幂运算
★★★ 挑战J0026https://hydro.ac/d/srqc/p/J0026综合:cin→变量→公式→类型选择→溢出判断

练习建议:先在课堂完成 1 道★核心题,掌握本章基本方法;课后完成 6 道◆拓展题,覆盖不同变式和细节;★★★挑战题建议在完成拓展题后再做,涉及多知识点综合。

自查清单

  • [ ] 我知道用哪个头文件引入数学函数
  • [ ] 我会用 abssqrtpow 进行计算
  • [ ] 我知道 maxmin 怎么用
  • [ ] 我能用 randsrand 生成随机数
  • [ ] 我知道 pow 有浮点误差,整数幂尽量直接乘
  • [ ] 我会查速查表找到我需要的函数
  • [ ] 我知道 ceilfloorround 的区别

🚀 下章预告

变量会用了,数学函数也会调了——第 1~7 章的知识点散落一地。下一章是 M1 的收官之战:把变量、类型、输入输出、运算、数学函数全部串起来,从零写出一个能用的计算器。准备迎接挑战!