🔷 第六章 函数
📄 一句话概括

可变参数 = 参数个数不固定:定义里写 ...(省略号),表示"后面还有若干个参数,来多少收多少"。取出它们靠 <cstdarg> 的四个工具:va_list / va_start / va_arg / va_end。最大的坑——函数自己数不出有几个参数,必须由调用方额外告诉它。

📝 本节和前面几节的关系

6.2.3 的默认参数解决"少传",这一节的省略号解决"多传"——两者合起来,就是把 6.2.2 那条"个数必须匹配"的规矩补出了两个例外。其实你早就用过可变参数了:2.5.3 见过的 printf("%d %s", a, b) 就是它最著名的用户。

一、写法

返回类型 函数名(固定参数表, ...)

注意两点: 1. ... 前面至少要有一个固定参数——不能写成 f(...),那个固定参数是"最后一块路标"。 2. ... 只是告诉编译器"这里开始不管了",具体有几个、都是什么类型,编译器一概不问(这正是它不安全的原因)。

二、四个工具(都在 <cstdarg> 里)

工具 干什么 比喻
va_list ap; 声明一个"取货清单" 拿着清单准备取货
va_start(ap, 最后一个固定参数); 把清单定位到第一个可变参数 站到货架起点
va_arg(ap, 类型); 取出一个,并声明它是什么类型 拿一件、顺手报一下是什么
va_end(ap); 用完收工 清单作废、柜台还原

顺序钉死:start → arg(可多次)→ end。

三、实例 03:定义可变参数函数

#include <iostream>
#include <cstdarg>                  // 可变参数的四件套都在这个头文件里
using namespace std;

int sum(int count, ...) {           // count:告诉函数"后面有几个数"
    va_list ap;                     // ① 取货清单
    va_start(ap, count);            // ② 站到起点(从 count 后面开始)
    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(ap, int);   // ③ 逐个取出,每次都要说清楚类型
    }
    va_end(ap);                     // ④ 收工
    return total;
}

int main() {
    cout << "两数之和: " << sum(2, 10, 20) << endl;
    cout << "三数之和: " << sum(3, 1, 2, 3) << endl;
    cout << "五数之和: " << sum(5, 1, 2, 3, 4, 5) << endl;
    return 0;
}

看到什么算成功:

两数之和: 30
三数之和: 6
五数之和: 15

关键就在第一个参数 count:函数必须靠它知道要取几次。 - 写成 sum(2, 10, 20, 30) → 只取两个,后面的 30 被无视(悄悄消失) - 写成 sum(5, 1, 2) → 去取不存在的东西(越界读取),结果乱七八糟甚至崩溃

这就是可变参数的原罪:没有长度信息,全靠调用方自觉。

30 秒实验:先正确调 sum(3, 5, 5, 5)(得 15),再故意调 sum(5, 5, 5, 5)(只给 4 个数却说 5 个)——输出很可能是垃圾值或程序直接崩。用身体记住"越界读"的后果(和第五章数组越界是同一类事故,第 7 章会正式讲)。

四、printf 就是可变参数函数

printf("%d 加 %d 等于 %d\n", 3, 5, 8);
部分 角色
"%d 加 %d 等于 %d\n" 唯一固定参数(格式串)
3, 5, 8 可变部分
格式串里的三个 %d "有几个、什么类型"的说明书

printf 正是靠格式串里的 % 个数决定取几次、靠 %d/%s 决定按什么类型取。这就解释了 2.5.3 那个坑:格式串写错(少写一个 %d 却多给一个参数,或者类型对不上),输出就是乱码——因为它是"照着自己说的去取",取错了也不会有人拦。

五、常见误区

  1. "可变参数可以没有固定参数"——不能,... 前面至少要有一个。
  2. "函数能自己数出有几个参数"——完全不能。这是它最根本的缺陷:要么让调用方传个数(count),要么用格式串说明(printf)。
  3. "类型可以随便指"——va_arg(ap, int) 的类型必须和实际传的严格一致。传了 double 却按 int 取,就是垃圾值,编译器不会帮你检查(可变参数完全没有类型检查)。
  4. "可变参数是最方便的传参方式"——相反,它不安全、难调试、容易越界。教材让你认识它,是为了看懂 printf/scanf 这类老函数;你自己写新函数时,优先考虑"数组 + 个数"(第 7 章)——安全得多。
自测三问(点开对答案) 1. 可变参数的四个工具是什么?——`va_list`、`va_start`、`va_arg`、`va_end`。 2. 函数怎么知道有几个可变参数?——**不知道**,必须由调用方额外说明(`count` 参数或格式串)。 3. `printf` 的格式串起什么作用?——告诉它有几个参数、各是什么类型(少写或多写都会导致取错)。

相关笔记