在 C++ 中,比较两个或多个数据的大小、找出其中的最大值或最小值是很常见的操作。虽然我们可以自己写 if...else 语句或三元运算符来判断,但直接调用标准库提供的现成函数会让代码简洁直观得多。
与前面几节的 <cmath> 头文件不同,C++ 求最大值与最小值的函数定义在 <algorithm> 头文件中,常用的主要有 3 个:① std::max();② std::min();③ std::minmax()。
std::max() 与 std::min()
在 C++ 中,当我们只需要比较两个已知数据的大小时,可以在引入 <algorithm> 头文件后直接调用 std::max() 和 std::min(),分别用来获取最大值和最小值。
语法:
std::max(a, b)
std::min(a, b)说明:
std::max(a, b) 会返回 a 和 b 中较大的值;std::min(a, b) 则会返回 a 和 b 中较小的值。它们不仅支持 int、double 等数值类型,还支持 char 字符和字符串按字典序比较大小。
小伙伴们要注意,比较字符串大小时需要先引入 <string> 头文件并传入 std::string 对象。千万不要直接传入 "apple"、"banana" 这样的双引号字符串字面量,否则比较的是内存地址而不是文本内容。
示例 1:比较两个数的大小
#include <algorithm>
#include <iostream>
int main() {
int a = 15;
int b = 28;
std::cout << std::max(a, b) << '\n'; // 求较大值
std::cout << std::min(a, b) << '\n'; // 求较小值
double x = 3.14;
double y = 2.71;
std::cout << std::max(x, y) << '\n'; // 比较浮点数
return 0;
}运行结果如下。
28
15
3.14分析:
std::max(a, b) 返回了较大的整数 28,std::min(a, b) 返回了较小的整数 15,对浮点数 x 和 y 也同样适用。
除了单纯比大小之外,这两个函数在实际开发中还经常用来给变量做 “保底” 或 “封顶”。比如游戏角色受到伤害扣血后,生命值 hp 最低只能扣到 0,不能变成负数。对于这个需求,下面 2 种方式是等价的。
int hp = 20 - 35;
// 方式 1(推荐)
hp = std::max(0, hp);
// 方式 2
if (hp < 0) {
hp = 0;
}其中,方式 1 通过 std::max(0, hp) 为生命值设置了下限:一旦 hp 小于 0,函数就会返回较大的 0,一行代码就能代替手写 if 判断。
std::clamp() 函数
在上面的扣血例子里,如果我们不仅要求生命值 hp 最低不能小于 0,还要求回血时最高不能超过满血上限 100,此时又应该怎么写呢?
从 C++17 标准开始,<algorithm> 头文件专门提供了 std::clamp(val, lo, hi) 函数,其中 clamp 是英文 “夹紧、固定” 的意思。当 val 小于下限 lo 时返回 lo,大于上限 hi 时返回 hi,处于区间内则原样返回 val。
比如直接写一句 hp = std::clamp(hp, 0, 100);,我们就能把 hp 限制在 0 ~ 100 之间,比嵌套写成 std::min(100, std::max(0, hp)) 好读得多。使用时只需要注意下限 lo 不能大于上限 hi 即可。
比较多个数与 std::minmax()
如果手头有 3 个、4 个甚至更多已知的变量,想要找出其中的最值,我们不需要一层套一层地写成 std::max(a, std::max(b, c)),<algorithm> 提供了更简便的多值比较写法。
1. 比较 3 个及以上的数
当需要同时比较多个同类型的数据时,我们只需要在参数位置写上一对花括号 {},把所有待比较的值放进去即可。
语法:
std::max({值1, 值2, 值3, ...})
std::min({值1, 值2, 值3, ...})说明:
花括号 {} 内可以放入任意多个同类型的变量或常量,数据之间用英文逗号隔开,函数会自动找出并返回其中的最大值或最小值。
示例 2:比较多个数的大小
#include <algorithm>
#include <iostream>
int main() {
int s1 = 85;
int s2 = 92;
int s3 = 78;
int s4 = 96;
int highest = std::max({s1, s2, s3, s4}); // 找出最高分
int lowest = std::min({s1, s2, s3, s4}); // 找出最低分
std::cout << highest << '\n';
std::cout << lowest << '\n';
return 0;
}运行结果如下。
96
78分析:
std::max({s1, s2, s3, s4}) 表示直接从花括号里的 4 个分数中找出最高分 96,而 std::min({s1, s2, s3, s4}) 则找出了最低分 78,比多层嵌套调用直观得多。
2. 同时获取最小值和最大值
在上面的例子中,为了同时拿到最高分和最低分,我们分别调用了一次 std::max() 和一次 std::min()。如果希望在一次函数调用中同时拿到最小值和最大值,我们可以使用 std::minmax() 函数。
语法:
std::minmax(a, b)
std::minmax({值1, 值2, 值3, ...})说明:
std::minmax() 既可以接收两个参数,也可以接收花括号列表来比较多个值。调用后,它会把找出来的最小值和最大值打包成一个 std::pair 对象返回,其中第 1 个成员 .first 固定是最小值,第 2 个成员 .second 固定是最大值。
示例 3:找出最低温与最高温
#include <algorithm>
#include <iostream>
int main() {
auto res = std::minmax({18, 26, 12, 23}); // 同时获取最小值和最大值
std::cout << "最低温:" << res.first << '\n';
std::cout << "最高温:" << res.second << '\n';
return 0;
}运行结果如下。
最低温:12
最高温:26分析:
在这个例子中,std::minmax({18, 26, 12, 23}) 返回了一个 std::pair 对象 res。小伙伴们可以顺着函数名 minmax 中 “先 min 后 max” 的顺序来记:第 1 个成员 res.first 是最小值 12,第 2 个成员 res.second 是最大值 26。
提示 std::max() 和 std::min() 适合比较已知的几个变量。如果想在一个存放了大量元素的数组或 std::vector 容器中查找最值,我们可以调用 STL 算法中的 std::max_element() 和 std::min_element() 函数来实现。
比较不同类型的数值
初学者在使用 std::max() 和 std::min() 时,经常会遇到这样一个编译报错:明明传进去的是 int 整数 10 和 double 小数 12.5,直接写 std::max(10, 12.5) 时编译器却提示找不到匹配的函数。
这是因为 std::max() 和 std::min() 底层是通过函数模板实现的,要求传入的两个参数类型必须一致。如果一个是 int、另一个是 double,编译器就无法确定应该按哪种类型来推导返回值。
遇到这种情况时,我们只需要在函数名后面加上 <double> 显式指定比较类型,写成 std::max<double>(a, b) 即可。请看下面例子。
示例 4:比较不同类型的数值
#include <algorithm>
#include <iostream>
int main() {
int a = 10;
double b = 12.5;
double res = std::max<double>(a, b); // 统一按 double 类型比较
std::cout << res << '\n';
return 0;
}运行结果如下。
12.5分析:
std::max<double>(a, b) 在函数名后面显式加上了 <double>,告诉编译器统一按 double 类型来比较。这样编译器会先把 int 类型的 a 转为 double,再与 b 比较并算出较大值 12.5。
下面这种不指定类型的写法就是错误的,由于两个参数的类型不一致,编译时会直接报错。
double wrongVal = std::max(a, b); // 参数类型不一致平时除了 int 与 double 比较之外,拿普通 int 变量与容器 .size() 返回的 size_t 比较大小时,同样会遇到类型不匹配的问题。遇到这种情况,大家显式加上 <size_t> 或 <int> 指定类型就能解决。
