HOWTO · C

C 语言中的 MIN 和 MAX 函数

可以使用宏或循环来实现 C 语言中的 MIN 和 MAX 函数。

本页内容

在 C 中查找两个值中的较小值或较大值,可以定义 MIN 或 MAX 宏,也可以编写函数。标准 C 不提供通用的内置 MIN 和 MAX 函数。对于数组,可以用第一个元素初始化结果,再扫描其余元素。

C 语言内置了 MIN 和 MAX 吗?

C 标准库没有定义通用的 MIN 和 MAX 函数。项目通常会将这些名称定义为宏或普通函数。如果参数带有副作用,函数更容易推理;对于能够安全求值的值,简单宏则更简洁。

#define 指令用于创建预处理器宏。预处理器遇到宏调用时,会在编译器处理程序之前将其替换为替换文本。函数调用则会调用已编译的代码,并按照 C 的函数调用规则求值参数。

为两个值定义 MIN 和 MAX 宏

对于两个不带副作用的整数表达式,应将每个参数和整个替换表达式都括起来。这样,调用者传入 a + b 之类的表达式时,也能保持预期的运算分组。

#include <stdio.h>

#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))

int main(void) {
  printf("Min = %d\n", MIN(0, 10));
  printf("Max = %d\n", MAX(0, 10));
  return 0;
}
Min = 0
Max = 10

条件运算符 ?: 会在比较结果为真时选择 ? 后面的表达式,否则选择 : 后面的表达式。括号可以处理运算符优先级,但无法避免重复求值:在 MIN(a, b) 中,被选中的参数会再次出现在替换文本里。不要传入 i++ 或会改变状态的函数调用等参数。这种宏适合简单的值;如果参数可能带有副作用,请改用函数。

使用 GNU C 宏让参数只求值一次

GNU C 支持语句表达式和 typeof(本例中写作 __typeof__)。下面的宏会将每个参数保存到临时变量中,再比较这些值。每个参数表达式在初始化临时变量时只求值一次。这属于 GNU C 扩展,并非可移植的 ISO C;可能还需要启用 GNU 扩展的编译器选项。

#include <stdio.h>

#define MAX(x, y)                    \
  ({                                 \
    __typeof__(x) _max_x = (x);      \
    __typeof__(y) _max_y = (y);      \
    _max_x > _max_y ? _max_x : _max_y; \
  })

#define MIN(x, y)                    \
  ({                                 \
    __typeof__(x) _min_x = (x);      \
    __typeof__(y) _min_y = (y);      \
    _min_x < _min_y ? _min_x : _min_y; \
  })

int main(void) {
  int a = 4;
  int b = 9;
  printf("Min = %d\n", MIN(a++, b));
  printf("Max = %d\n", MAX(a, b));
  return 0;
}
Min = 4
Max = 9

这种写法适用于可用 < 或 > 比较的普通标量表达式。它并不是适用于所有类型的泛型函数:类型不兼容、没有合理排序关系的指针,以及不支持这些运算符的类型,都需要采用其他设计。条件表达式也遵循 C 的常规类型转换规则。若要编写可移植代码,请使用类型明确的函数,或为支持的类型分别编写函数。

查找数组中的最小值和最大值

遍历数组时,保存当前的最小值和最大值,并在遇到更小或更大的元素时更新它们。用第一个元素初始化两者,确保结果来自数组中的实际值。示例使用与原数组示例相同的数据,只扫描数组一遍。

#include <stdio.h>
#include <stddef.h>

void find_min_max(const int values[], size_t length, int *minimum, int *maximum) {
  *minimum = values[0];
  *maximum = values[0];

  for (size_t i = 1; i < length; ++i) {
    if (values[i] < *minimum) {
      *minimum = values[i];
    }
    if (values[i] > *maximum) {
      *maximum = values[i];
    }
  }
}

int main(void) {
  int values[] = {6, 3, 15, 9, 10, 50, 99, 11};
  size_t length = sizeof(values) / sizeof(values[0]);
  int minimum;
  int maximum;

  find_min_max(values, length, &minimum, &maximum);
  printf("Max number = %d\n", maximum);
  printf("Min number = %d\n", minimum);
  return 0;
}
Max number = 99
Min number = 3

此函数要求数组非空,且结果指针有效。调用者需要传入数组长度,因为数组形参会调整为指针;在 find_min_max 中,sizeof(values) 测量的是指针,而不是原数组。这里在 main 中计算元素个数,此时 values 仍是数组:用数组的总大小除以单个元素的大小即可。

处理浮点数据时,数组、最小值和最大值变量以及函数参数应使用一致的类型。printf 的转换说明符应与提升后的参数类型匹配;对于 double,使用 %f。如果函数需要接受多种数值类型,应编写类型合适的函数,或设计周全的抽象,而不是只把 int 改成 float。