知识点 07 · 函数、递归与变量存储类别¶
本页最后更新于 ,内容可能过时
1 · 值传递与地址传递¶
在 C 语言中,以下关于函数参数传递的说法哪一个是正确的?
- A. 值传递不会改变实参的值
- B. 参数是结构体指针时,需要传递整个结构体的数据
- C. 指针传递不能用于传递数组
- D. 数组作为函数参数时,传递的是数组的副本
答案
A
解析
逐项排雷——
- A 对:值传递是把实参的副本给形参,函数内怎么改都不影响实参。注意这句话成立的前提:改的是形参本身。传指针时"改指针指向的内容"照样能影响实参,那不叫"改实参的值"(见下表第 2 行)。
- B 错:传结构体指针恰恰是为了避免拷贝整个结构体。
- C 错:数组名退化为指针,指针传递正是传数组的标准方式。
- D 错:数组作参数时传递的是首元素地址,形参和实参是同一块内存,函数内修改会直接影响原数组。这是 C 最反直觉的一点。
两张对照表:
传递方式 形参 能否改实参的值 能否改实参指向的数据 void f(int x)副本 ❌ — void f(int *p)地址副本 ❌(改 p无效)✅(改 *p有效)void f(int a[])首地址 ❌ ✅
2 · 值传递与地址传递(改错)¶
/* 1 */void swap(short *x, short *y) {
/* 2 */ short *z;
/* 3 */ z = *x;
/* 4 */ *x = *y;
/* 5 */ *y = z;
/* 6 */}
/* 7 */int main() {
/* 8 */ short a=3, b=5;
/* 9 */ swap(a, b);
/* 10 */ printf("a=%hd, b=%hd\n", a, b);
/* 11 */}
要求运行结果为 a=5, b=3。请指出错误行并写出正确语句(不增删语句、不改程序结构)。
答案
共 2 处错误:
| 行 | 错误 | 改正 | 错因 |
|---|---|---|---|
| 2 | short *z; |
short z; |
暂存变量只需存放一个 short 值,不该声明成指针 |
| 9 | swap(a, b); |
swap(&a, &b); |
形参是 short *,必须传地址,否则类型不匹配 |
解析
- 只要把第 2 行改成
short z;,第 3 行z = *x;(取出*x的值存进z)、第 5 行*y = z;(把值写回*y)就都对了——这是一次完整的"通过指针交换两个变量"。 - 第 9 行是本课程最经典的错误:
swap(a, b)传的是值的副本,函数里怎么改都影响不到main里的a、b(形参x、y是另外两个变量,函数一返回就消失)。要改实参,必须传地址,所以只能写swap(&a, &b)。 - 另一条改法的坑(别走):如果把第 3 行改成
z = x;、第 5 行改成*y = *z;(即把z当指针用),实测结果是错的:z和x指向同一处,*x = *y一执行,*z读到的就已经是新值了,*y = *z退化成自赋值,真机跑出来是a=5, b=5,不是题目要的a=5, b=3。所以本题只有"改第 2 行一行"这一条路——改错题优先选改动最小的方案。 - 只要第 2 行改对了,第 9 行就一定还得加上
&;两处错误的本质是同一个:把"值"和"地址"搞混了。
材料未附答案
材料里这一题没有附答案,下面的结果是推导(并真机核对)得出的,建议自己编译验证一遍。
材料上留存的是一份个人作答,其中也纠结于 z = x / x = y 之类,但没有写出完整正确的改法。
3 · 值传递与地址传递(改错)¶
/* 第 1 行 */ void func(int *a, int *b)
/* 第 2 行 */ {
/* 第 3 行 */ int *x;
/* 第 4 行 */ x = *b;
/* 第 5 行 */ *b = *a;
/* 第 6 行 */ *a = x;
/* 第 7 行 */ }
/* 第 8 行 */ int main()
/* 第 9 行 */ {
/* 第 10 行 */ int u=8, v=4;
/* 第 11 行 */ func(u, v);
/* 第 12 行 */ printf("u=%d, v=%d\n", &u, &v);
/* 第 13 行 */ return 0;
/* 第 14 行 */ }
要求运行结果为 u=4, v=8。请指出错误行并写出正确语句(不增删语句、不改程序结构)。
答案
共 3 处错误:
| 行 | 错误 | 改正 | 错因 |
|---|---|---|---|
| 3 | int *x; |
int x; |
暂存一个 int 值,不需要指针 |
| 11 | func(u, v); |
func(&u, &v); |
形参是 int *,必须传地址 |
| 12 | printf("u=%d, v=%d\n", &u, &v); |
printf("u=%d, v=%d\n", u, v); |
%d 要的是 int 值,&u 是地址 |
解析
- 改完第 3 行之后,第 4 行
x = *b;(把*b的值存进x)、第 6 行*a = x;(把x写回*a)就都对了——这是一次完整的"通过指针交换两个变量"的实现。 - 第 11 行和第 12 行的错因是同一个:把"值"和"地址"写反了。取地址运算符
&的正确用法是"调用时给形参指针(func(&u, &v))",而不是"打印时给%d(那是u, v)"。两个方向的误用都是本课程的高频陷阱。 - 本题的形参是
short *x, short *y,错误是"暂存变量写成了指针" + "调用时没传地址",另外多送了一个printf里误用&。同一个点反复考,说明它是必考项。
材料未附答案
材料里这一题没有附答案,下面的结果是推导(并真机核对)得出的,建议自己编译验证一遍。
4 · 递归的执行顺序(回溯)¶
请写出以下程序的运行结果。
#include <stdio.h>
void sub(int *a, int n, int k)
{
if (k <= n)
sub(a, n / 2, 2 * k);
*a += k;
}
void main()
{
int x = 0;
sub(&x, 4, 1);
printf("%d\n", x);
}
答案
7
解析
注意 *a += k 写在递归调用之后,所以累加发生在回溯阶段("归"的时候),由内向外:
| 调用 | k <= n? |
说明 |
|---|---|---|
sub(&x, 4, 1) |
1 ≤ 4 ✓ | 先递归 |
↳ sub(&x, 2, 2) |
2 ≤ 2 ✓ | 先递归 |
↳↳ sub(&x, 1, 4) |
4 ≤ 1 ✗ | 不递归,x += 4 → x = 4 |
↳ x += 2 |
x = 6 |
|
x += 1 |
x = 7 |
最终输出 7。(已用 gcc 15.2.0 实测确认。)
易错:把递归写成循环去推,得到
x += 1+2+4 = 7—— 答案碰巧一样,但过程理解是错的(本题恰好是累加,顺序无关;换成*a = *a * 2 + k之类就完全不同了)。
*a是"改实参指向的内容"的范例:a是形参指针(副本),但*a指向的就是main里的x。
材料未附答案
材料里这一题没有附答案,下面的结果是推导(并真机核对)得出的,建议自己编译验证一遍。
5 · 递归的执行顺序(回溯)¶
请写出以下程序的运行结果。
#include <stdio.h>
void add(int *a, int n, int k) {
if (k > 0) {
add(a, n - 1, k - 1);
}
*a += (n + k);
}
int main() {
int x = 0;
add(&x, 3, 2);
printf("%d\n", x);
return 0;
}
答案
9
解析
*a += (n+k) 在递归调用之后,所以累加发生在回溯阶段:
| 调用 | k > 0? |
回溯时的 *a += (n+k) |
|---|---|---|
add(&x, 3, 2) |
2 > 0 ✓ 先递归 | x += 5 |
↳ add(&x, 2, 1) |
1 > 0 ✓ 先递归 | x += 3 |
↳↳ add(&x, 1, 0) |
0 > 0 ✗ 不递归 | x += 1 ← 最先执行 |
执行顺序是 1 → 3 → 5,累计 x = 1 + 3 + 5 = 9。(已用 gcc 15.2.0 实测确认。)
换个参数递减方式,
sub的函数结构完全一样(同样是"递归 +*a += k")——两种写法都要会,才能真正吃透"递归中语句位置决定执行顺序"这个考点。
材料未附答案
材料里这一题没有附答案,下面的结果是推导(并真机核对)得出的,建议自己编译验证一遍。
另外,材料上这一题记的是 *4,与按程序实际运行得到的结果不一致(gcc 15.2.0 实测输出 9),以实测结果为准。
6 · static 局部变量 + 递归¶
请写出以下程序运行所产生的输出。
#include <stdio.h>
void func(int n)
{
static int count = 0;
if (n > 0) {
count++;
func(n - 1);
printf("%d ", count);
}
}
int main(void)
{
func(3);
printf("\n");
func(2);
return 0;
}
解析
两个关键点——
① static 局部变量只初始化一次,且生命周期贯穿整个程序。 所以第二次调用 func(2) 时 count 从 3 接着数,而不是从 0 重来。
② printf 写在递归调用之后,所以是"归"的时候打印,顺序是逆的。
func(3):
- 调用链
func(3) → func(2) → func(1) → func(0),count依次变成 1、2、3; func(0)时n > 0不成立,直接返回(不打印);- 回溯时打印:
func(1)打印3、func(2)打印3、func(3)打印3→3 3 3
func(2):
count从 3 继续 → 4、5;- 回溯打印
5 5
合计输出 3 3 3 换行 5 5。
易错:① 把
count当成普通局部变量(每层递归一份)——static变量全程只有一份;② 以为打印是"递"的时候发生(那样会输出
1 2 3);③ 以为第二次调用会重新从 1 开始(那样会输出
1 2)。语句在递归调用前 = 递推时执行(正序);在递归调用后 = 回归时执行(逆序)。
7 · 递归出口与无限递归¶
/* 第 1 行 */ void digit(int n) {
/* 第 2 行 */ char ch;
/* 第 3 行 */ if(n < 10) {
/* 第 4 行 */ printf("%c", n + "a");
/* 第 5 行 */ }
/* 第 6 行 */ else {
/* 第 7 行 */ printf("%c", n % 10 + 'a');
/* 第 8 行 */ digit(n);
/* 第 9 行 */ }
/* 第 10 行 */ }
/* 第 11 行 */ int main() {
/* 第 12 行 */ digit(1234);
/* 第 13 行 */ return 0;
/* 第 14 行 */ }
要求 digit(1234) 输出 edcb。请指出错误行并写出正确语句(不增删语句、不改程序结构)。
答案
共 2 处错误:
| 行 | 错误 | 改正 | 错因 |
|---|---|---|---|
| 4 | printf("%c", n + "a"); |
printf("%c", n + 'a'); |
双引号 "a" 是字符串(地址),不能和整数相加;要用单引号的字符常量 'a' |
| 8 | digit(n); |
digit(n / 10); |
递归参数没有缩小 → 无限递归,永远不会到达 n < 10 的出口 |
解析
- 第 4 行的
"a"vs'a':这是本课程的送分/送命点。n + "a"是"整数 + 指针",类型完全错;即使能算,得到的也是地址偏移而不是字符。 - 第 8 行的递归边界:递归函数必须让参数朝终止条件收敛。这里每次要除以 10,才能从 1234 → 123 → 12 → 1,进入
n < 10分支。写成digit(n)就是自己调自己、参数不变 → 无限递归(栈溢出)。 - 注意
%c打印的是字符:1234 % 10 + 'a'=4 + 97=101→'e'。
执行过程(修正后):
digit(1234): 1234 ≥ 10 → 打印 '4'+'a' = 'e',再 digit(123)
digit(123) : 打印 '3'+'a' = 'd',再 digit(12)
digit(12) : 打印 '2'+'a' = 'c',再 digit(1)
digit(1) : 1 < 10 → 打印 '1'+'a' = 'b'
输出 edcb ✅(本题的打印在递归调用之前,所以是"递"的时候按高位到低位正序输出。)
顺带一提:第 2 行
char ch;声明了却没用——不算错误(编译只是警告),改错题不要改这种"无用但合法"的语句,会超出题目的错误数量限制。
材料未附答案
材料里这一题没有附答案,下面的结果是推导(并真机核对)得出的,建议自己编译验证一遍。
8 · static / 存储类别¶
关于静态变量,以下哪项说法是正确的?
- A. 静态变量在函数调用结束后会被销毁
- B. 静态变量在程序运行期间只初始化一次
- C. 函数中定义的静态变量可以在不同的函数之间共享
- D. 静态变量必须在函数外部声明
答案
B
解析
- A 错:
static局部变量的生命周期是整个程序,函数返回后它依然存在(只是作用域出了函数)。 - B 对:初始化只在第一次进入时执行一次,之后每次进入保留上次的值。
- C 错:
static局部变量的作用域仍限于本函数,别的函数看不见(想跨函数共享要用文件作用域的全局static)。 - D 错:
static既可以修饰全局变量/函数(限定文件内可见),也可以修饰局部变量。
区分"作用域"与"生命周期"——这是本考点的核心:
作用域 生命周期 普通局部变量 函数内 函数执行期间 static局部变量函数内 整个程序 全局变量 本文件(加 static)或全程序整个程序
9 · 存储类别¶
如果希望调用某一函数时,使该函数内某一变量拥有最近一次退出该函数时所拥有的值,同时不希望其他函数访问该变量,则该变量应定义为什么存储类型?
- A.
auto - B.
register - C.
extern - D.
static
答案
D(static)
解析
题目给了两个条件,正好对上 static 局部变量的两个特性:
- "保留上次退出时的值" → 需要生命周期贯穿整个程序 → 排除
auto、register; - "不希望其他函数访问" → 需要作用域限于本函数 → 排除
extern(extern恰恰是声明"别处定义的全局变量")。
四个存储类别的速查:
| 存储类别 | 存放位置 | 生命周期 | 作用域 | 初值 |
|---|---|---|---|---|
auto(默认) |
栈 | 函数执行期间 | 函数内 | 不确定(垃圾值) |
register |
寄存器(建议) | 函数执行期间 | 函数内 | 不确定 |
static 局部 |
静态区 | 整个程序 | 函数内 | 自动为 0 |
extern / 全局 |
静态区 | 整个程序 | 全程序 | 自动为 0 |
易错:把"不被其他函数访问"理解成
extern。extern是专门用来"跨文件访问"的,方向正好相反。
10 · extern 与全局变量¶
执行下列程序,输出结果是__。
#include <stdio.h>
int x=5;
int func(int x1,int x2){
extern int x;
x1>x2?(x=3):(x=4);
return x+x1;
}
void main(){
printf("%d",func(6,7));
}
- A. 9
- B. 10
- C. 11
- D. 12
答案
B(10)
解析
函数里的 extern int x; 是声明而不是定义,它让这一行的 x 绑定到全局变量 x(值 5),不会新开一个局部变量。
调用 func(6,7) 时 6>7 为假 → 执行 (x=4),把全局 x 改成 4,于是 return x + x1 = 4 + 6 = 10。
排雷点在于形参叫 x1、全局叫 x,漏看 extern 就会以为 x 是个没定义的局部变量;另外别忘了 x1 是形参(值 6)而不是全局的 5。
11 · 全局变量的作用域¶
以下代码中的变量 a 具有什么作用域?
- A.
a仅在函数func内有效 - B.
a仅在函数外有效 - C.
a在整个程序中有效 - D.
a在func中无效
答案
C
解析
a 写在所有函数之外,是全局变量,从定义处起在本文件内处处可见,各函数内部都能直接引用——func 里 printf("%d", a) 完全合法,所以选 C。
B 和 D 都把"作用域在函数外"理解成了"函数内用不了",方向正好说反;对照 func 里的 b,那才是只在函数内有效的局部变量。
严格说跨文件访问还需 extern 声明,考试按"整个程序有效"记即可。
12 · 存储类别¶
未指定存储类别的变量,其隐含的存储类别为( )。
- A.
auto - B.
static - C.
extern - D.
register
答案
A(auto)
解析
C 规定:函数内部的局部变量不写存储类别时,隐含就是 auto——存放在栈上,函数返回即销毁,初值不确定。
static、extern、register 三者的语义都必须显式写出才会生效,标准不会自动给你。
注意这条"默认 auto"只针对局部变量,别和函数外定义的全局变量(具有外部链接、可被 extern 引用)混为一谈。
13 · static 局部变量跨调用保留¶
写出以下程序的运行结果(题面要求:空格用 □ 表示)。
#include <stdio.h>
int fun(int x)
{
int y = 0;
static int z = 3;
z++; ++y;
return(x + y + z);
}
void main()
{
int i, x = 2;
for (i = 0; i < 2; i++)
printf("%5d", fun(x++));
}
解析
%5d = "宽度 5、右对齐",所以每个数前补 4 个空格。真正要算的是两次 fun 的返回值,关键差别在这三个变量身上:
| 变量 | 声明 | 每次进入函数时 | 跨调用是否保留 |
|---|---|---|---|
x |
形参 | 由实参决定 | 不保留(每次都是新的形参) |
y |
int y = 0; |
重新执行 y = 0,之后 ++y → 1 |
❌ 每次重置 |
z |
static int z = 3; |
只在程序启动时初始化一次,z++ 在上次的值上继续 |
✅ 全程保留 |
调用过程表:
| 第几次调用 | main 里的 x |
传入的 x |
y(重置 0 → ++y) |
z(z++ 前 → 后) |
返回 x+y+z |
|---|---|---|---|---|---|
第 1 次 fun(x++) |
2 → 3 | 2 | 0 → 1 | 3 → 4 | 2+1+4 = 7 |
第 2 次 fun(x++) |
3 → 4 | 3 | 0 → 1 | 4 → 5 | 3+1+5 = 9 |
- 循环只跑 2 次(
i = 0, 1),所以只调用两次fun。 x++是后缀自增:先把旧值传给fun,回到main之后x才加 1,所以两次传入的是 2 和 3(不是 3 和 4)。static int z = 3;的初始化只在程序启动时做一次;第 2 次进fun时z已经是 4,z++得 5。
易错:① 把
z当普通局部变量,算成7 7;② 以为z++每次都从 3 开始;③ 把x++看成++x,算成两次传 3、4。
static局部变量的作用域仍在函数内,生命周期却是整个程序:初始化只做一次,之后每次进来接着用上次的值。顺带一提:题干里的
main()没写返回类型(当年的教材风格),现在应写int main(void)并return 0;。
14 · 值传递与全局变量¶
写出以下程序的运行结果(题面要求:空格用 □ 表示)。
#include <stdio.h>
int a1 = 300, a2 = 400;
void sub1(int x, int y)
{
a1 = x;
x = y;
y = a1;
}
void main()
{
int a3 = 100, a4 = 200;
sub1(a3, a4);
sub1(a1, a2);
printf("%d,%d,%d,%d\n", a1, a2, a3, a4);
}
答案
100,400,100,200
解析
sub1 的函数体看着像"交换"(a1 = x; x = y; y = a1;),其实什么都没换——它手里只有实参的副本,真正被改掉的是全局变量 a1。
三条语句的实际效果:
| 语句 | 实际效果 |
|---|---|
a1 = x; |
把全局 a1 改成形参 x 的值 ← 唯一真正生效的修改 |
x = y; |
只改形参 x(副本),函数一返回就消失 |
y = a1; |
只改形参 y(副本),同样失效 |
逐步跟踪:
| 步骤 | a1(全局) |
a2(全局) |
a3(main 局部) |
a4(main 局部) |
说明 |
|---|---|---|---|---|---|
| 初值 | 300 | 400 | 100 | 200 | |
调用 sub1(a3, a4) |
300 | 400 | 100 | 200 | 形参 x=100, y=200(副本) |
↳ a1 = x; |
100 | 400 | 100 | 200 | 全局 a1 被改写(300 → 100) |
↳ x = y; |
100 | 400 | 100 | 200 | 只动副本 |
↳ y = a1; |
100 | 400 | 100 | 200 | 只动副本(此时 a1 已是 100) |
调用 sub1(a1, a2) |
100 | 400 | 100 | 200 | 实参取当前值:x=100, y=400 |
↳ a1 = x; |
100 | 400 | 100 | 200 | 把 100 再写回 a1,值不变 |
↳ x = y; y = a1; |
100 | 400 | 100 | 200 | 还是只动副本 |
printf |
100 | 400 | 100 | 200 | 输出 100,400,100,200 |
a3、a4自始至终没变(100、200)——值传递的铁证:形参x/y只是副本,函数里怎么折腾都影响不到实参。想改实参必须传地址(void sub1(int *x, int *y)+sub1(&a3, &a4))。a2也没变(400):它只是第二次调用的实参,函数体里没人碰全局a2。- 变了的只有
a1:300 → 100,而且是第一次调用改的;第二次调用只是把 100 又写了一遍。 - 第二次调用
sub1(a1, a2)的实参在调用前求值,此时a1已经是 100,所以传进去的是 100 和 400。
易错:① 以为
sub1是交换函数,答成100,400,200,100——注意它没有一条语句是通过指针写回实参的,x、y全是形参;② 以为第二次调用传的是
a1的初值 300,答成300,400,100,200;③ 没看出
a1是全局变量,以为函数里的a1 = x是在改形参。顺带一提:题干里的
main()没写返回类型,现在应写int main(void)。
15 · 递归辗转相减求最大公约数¶
写出以下程序的运行结果。
#include <stdio.h>
int gcd(int m, int n)
{
if (m == n)
return m;
else if (m > n)
return gcd(m - n, n);
else
return gcd(m, n - m);
}
void main()
{
printf("Gcd=%d\n", gcd(24, 36));
}
答案
Gcd=12
解析
gcd 用的是辗转相减法(更相减损术),三条分支各司其职:
| 分支 | 含义 | 动作 |
|---|---|---|
m == n |
递归出口 | 返回 m(两数相等,它就是公约数) |
m > n |
大数减小数 | gcd(m - n, n) |
m < n |
大数减小数 | gcd(m, n - m) |
每递归一层,两数中较大的那个就被减掉较小的那个,两数之和严格变小,所以一定会收敛到 m == n,不会无限递归。
gcd(24, 36) 的完整调用过程:
| 层 | 调用 | 比较 | 走向 |
|---|---|---|---|
| ① | gcd(24, 36) |
24 < 36 | → gcd(24, 36-24) = gcd(24, 12) |
| ② | gcd(24, 12) |
24 > 12 | → gcd(24-12, 12) = gcd(12, 12) |
| ③ | gcd(12, 12) |
12 == 12 ✅ | return 12 ← 递归出口 |
| 回溯 | ①② 逐层把 12 原样传回 main |
所以 printf("Gcd=%d\n", gcd(24, 36)) 输出 Gcd=12。验算:24 = 12×2,36 = 12×3,12 确实是最大公约数。
易错:① 把两个分支的条件记反(必须在
m > n时才做gcd(m-n, n),否则会减出负数或 0);② 漏掉
m == n这个出口,以为会无限递归;③ 数错层数——本题只递归两层就到底了,不需要减很多次(因为 36-24=12 一步就把大数砍到位)。
注意排版:题干里
else后面的return gcd(m, n - m);被挤到了函数的}之外(页边排版噪声),已按 C 语法还原到else分支下。顺带一提:题干里的
main()没写返回类型,现在应写int main(void)。
16 · static 变量与自动变量对照¶
写出以下程序的运行结果。
#include <stdio.h>
main()
{
int i; for (i = 1; i <= 3; i++)
f(i);
}
int f(int j)
{
static int a = 10;
int b = 1;
b++;
printf("%d,%d,%d\n", a, b, j);
a += 10;
}
解析
本题考的就是同一个函数里 static 变量与普通(自动)局部变量的对照:
| 变量 | 声明 | 初始化时机 | 每次调用的表现 |
|---|---|---|---|
a |
static int a = 10; |
只在第一次调用前做一次 | 在上次的结果上继续(a += 10) |
b |
int b = 1; |
每次调用都重新执行 | 永远 1 → 2,所以打印出来的 b 恒为 2 |
j |
形参 | 由实参决定 | 1、2、3 |
调用过程表:
| 第几次调用 | j |
a(打印时) |
b(1 → ++b) |
打印 | 打印后 a += 10 |
|---|---|---|---|---|---|
f(1) |
1 | 10 | 1 → 2 | 10,2,1 |
a:10 → 20 |
f(2) |
2 | 20 | 1 → 2 | 20,2,2 |
a:20 → 30 |
f(3) |
3 | 30 | 1 → 2 | 30,2,3 |
a:30 → 40(程序结束,不再使用) |
三个要点:
a每次 +10:打印的是加 10 之前的值(a += 10写在printf后面),所以是 10、20、30,不是 20、30、40。b恒为 2:int b = 1;每进一次函数都重新执行,b++后永远是 2——自动变量不跨调用保留。j就是 1、2、3:main里for (i = 1; i <= 3; i++) f(i);依次调用f(1)、f(2)、f(3)。
易错:① 把
a也当成每次重置,答成三行都是10,2,?;② 心算时把
a += 10提到printf前面,答成20,2,1/30,2,2/40,2,3;③ 把
b当成 static,答成 2、3、4。小坑:材料上逗号用的是全角"
,"(10,2,1),但printf的格式串"%d,%d,%d\n"用的是半角逗号,实际输出是10,2,1——真机验证确认。一句话区分:
static局部变量 = 初始化只有一次,值活到程序结束;自动局部变量 = 每次进函数重新出生,出函数就死。两者作用域完全一样(都只在本函数内可见),差的只是生命周期。老写法提醒:题干用的是老教材风格(
main()没写返回类型,且f定义在main之后没有事先声明)。现在应写
int main(void),并在main之前加int f(int j);声明(或把f的定义挪到main前面);否则 gcc 15.2.0 会直接报
implicit declaration of function 'f'错误(验证时对这种老写法加了-fpermissive才跑通)。