金穗
← 返回

指针与递归:小白也能懂的c语言教程

指针与递归:小白也能懂的 C 语言教程

一、指针

先建立画面

内存就是一排格子,每个格子有一个地址,每个格子里存数据

地址:  0x1000   0x1004   0x1008
       ┌──────┐ ┌──────┐ ┌──────┐
值:    │  42  │ │ 100  │ │0x1000│
       └──────┘ └──────┘ └──────┘
变量:    a        b        p(指针)

指针就是:一个专门用来存”地址”的变量。

p 里存的是 a 的地址(0x1000),所以说”p 指向 a”。


三个关键符号

符号含义比喻
&a取 a 的地址”告诉我 a 住哪儿”
int *p声明 p 是指针”p 是个存地址的变量”
*p解引用,跟着地址找到那个格子”去 p 指向的地方”

一句话记住:p 是门牌号,*p 是房间里的东西。


代码示例

int a = 42;      // a 在地址 0x1000,值是 42
int *p = &a;     // p 存的是 a 的地址(0x1000)

printf("%d\n", a);   // 输出 42
printf("%d\n", *p);  // 也输出 42(通过地址找到 a)

*p = 99;         // 通过 p,把 a 的值改成 99
printf("%d\n", a);   // 输出 99,a 真的被改了!

常见用途

1. 函数修改外部变量

// ❌ 这样不行,函数拿到的是副本,改不了外面的 x
void add_one(int n) {
    n = n + 1;
}

// ✅ 传地址进去,才能真正修改
void add_one(int *n) {
    *n = *n + 1;
}

int x = 5;
add_one(&x);
printf("%d\n", x);  // 输出 6

2. 指针和数组

数组名本身就是指针,指向第一个元素的地址。

int arr[3] = {10, 20, 30};
int *p = arr;          // p 指向 arr[0]

printf("%d\n", *p);    // 10
printf("%d\n", *(p+1));// 20(向后移动一格)
printf("%d\n", p[2]);  // 30(也可以用下标)

⚠️ 最容易犯的错:野指针

// ❌ 危险!p 没有初始化,指向随机地址
int *p;
*p = 99;  // 程序崩溃!

// ✅ 声明时赋值,或者先置为 NULL
int a = 42;
int *p = &a;   // 安全
// 或者
int *q = NULL; // 先置空,后续再赋值

二、递归

核心思想

递归就一句话:函数自己调用自己,但每次问题变小一点,直到遇到最简单的情况(出口)就停下来。


用阶乘来理解

5! = 5 × 4 × 3 × 2 × 1

递归的思路:

  • factorial(5) = 5 × factorial(4)
  • factorial(4) = 4 × factorial(3)
  • factorial(3) = 3 × factorial(2)
  • factorial(2) = 2 × factorial(1)
  • factorial(1) = 1出口,直接给答案,不再往下问

然后再一层层往回返回结果:

调用阶段(展开)              返回阶段(收回)
────────────────────────────────────────────
factorial(5) → 等待...    ← 5 × 24 = 120 ✓
  factorial(4) → 等待...  ← 4 × 6 = 24
    factorial(3) → 等待...← 3 × 2 = 6
      factorial(2) → 等待...← 2 × 1 = 2
        factorial(1) = 1  ← 出口!直接返回

对应的代码

int factorial(int n) {
    if (n == 1) return 1;       // 出口:最简单的情况
    return n * factorial(n-1);  // 递归:问题变小一点
}

int result = factorial(5);
printf("%d\n", result);  // 输出 120

写递归只需要想两件事

  1. 出口是什么? 什么时候直接给答案,不再往下问
  2. 大问题怎么变成小问题? factorial(n) 怎么用 factorial(n-1) 表达

再看一个例子:斐波那契数列

F(n) = F(n-1) + F(n-2),前两项是 1。

1, 1, 2, 3, 5, 8, 13, 21 ...
int fib(int n) {
    if (n == 1 || n == 2) return 1;  // 出口:前两项都是 1
    return fib(n-1) + fib(n-2);      // 递归:用前两项之和
}

printf("%d\n", fib(6));  // 输出 8

⚠️ 最容易犯的错:忘记出口

// ❌ 危险!没有出口,无限递归,程序崩溃(栈溢出)
int factorial(int n) {
    return n * factorial(n-1);  // 一直调用,永远不停
}

// ✅ 一定要有出口
int factorial(int n) {
    if (n == 1) return 1;       // 出口在这里!
    return n * factorial(n-1);
}

三、两者对比总结

指针递归
核心是什么存地址,通过地址操作数据函数调用自己,问题逐渐变小
关键操作& 取地址,* 解引用找出口,定递推关系
最常见的坑野指针(未初始化就使用)忘记出口(无限递归)
调试思路打印地址和值,确认指向正确打印每层的参数和返回值

四、快速记忆口诀

指针:

& 取地址,* 去找它,p 是门牌,*p 是家。

递归:

先找出口,再缩问题;一层层下去,一层层回来。

· · ·