本讲定位:CSP-J 初赛系列的地基讲。七年真题统计(见
cspj-exam-analysis-2019-2025.md)里,递归类程序段出了 4 段(分形生成、汉诺塔、递归展开求值、递归 vs 递推),分布均匀、折算全卷权重约 $10 \pm 4$ 分;选择题”递归求值”2025 年出到 4 分;“递归不终止”是阅读程序判断题五大陷阱之一(2021、2024 两考)。更重要的是:第 09 讲阅读程序、第 10 讲完善程序都默认你”会读递归”——读不懂递归,DP 段、汉诺塔段、gcd 段全部无从下手。本讲把这块地基一次打牢。初赛考递归的三种姿势,本讲各配专门小节:选择题手算(给递归定义求 $f(某值)$)、阅读程序(画调用树、精确数调用次数、判断会不会不终止)、完善程序(挖递归出口和递归调用的参数)。
前置知识:数组、
for循环、第 01 讲的栈(函数调用本质上就是栈)。适合对象:CSP-J 初赛。标注 ⭐ 的小节为进阶内容,第一遍学习可以跳过。
#include<iostream>
using namespace std;
int maxOfThree(int a, int b, int c) { // 返回值类型 函数名(参数表)
int m = a;
if (b > m) m = b;
if (c > m) m = c;
return m; // return:带着结果回到调用处
}
int main() {
cout << maxOfThree(3, 7, 5) << endl; // 输出 7
return 0;
}
三个组成部分逐一点名:
void,此时 return; 单独一句表示”提前结束”。a, b, c 叫形参,调用处的 3, 7, 5 叫实参。调用的瞬间,实参的值被复制给形参——这个”复制”两个字是下一节全部内容的伏笔。return 立即生效:执行到 return,函数马上结束,后面的语句不再执行。阅读程序时看到循环体里的 return,要意识到”这是找到答案就跑路”。一条初赛常考的细节:声明了返回 int 的函数必须在所有路径上 return;main 函数例外,不写 return 0; 也合法。
实参复制给形参之后,形参就是一份独立的复印件——函数内部怎么改,都动不到调用处的原件。经典的翻车现场:
void swapWrong(int a, int b) { // a、b 是 x、y 的复印件
int t = a; a = b; b = t; // 交换的是复印件
}
int main() {
int x = 1, y = 2;
swapWrong(x, y);
cout << x << " " << y << endl; // 仍然输出 1 2!
return 0;
}
这道”为什么 swap 不起作用”是选择题与判断题的常客。记住结论:传值调用下,函数对形参的任何修改在函数结束时全部作废。
&:函数里改的是”本尊”在形参类型后面加 &,形参就不再是复印件,而是实参的别名——改它就是改原变量:
void swapRight(int &a, int &b) { // a 就是 x、b 就是 y 的别名
int t = a; a = b; b = t;
}
int main() {
int x = 1, y = 2;
swapRight(x, y);
cout << x << " " << y << endl; // 1 2 变成了 2 1
return 0;
}
阅读程序头号陷阱就藏在这里:函数签名里那个不起眼的 &,决定了”调用之后外面的变量变没变”。读程序先扫一眼参数表——int n 是复印件,int &n 是本尊,vector<int> v 是把整个容器复制一份(还很慢),vector<int> &v 才是原容器。2022 年起选择题的 C++ 语法题(每年恒定 4 分)就爱在引用和指针上做文章。
数组是例外:数组名作参数时传的是首地址,函数内改 a[i] 就是改原数组——不加 & 也是”本尊”。这一条和上面 & 的规则合起来,覆盖了初赛程序里所有”函数改外面”的情况。