C 语言书
模块三 · 进阶与实战 / 第 13 课

13 · 指针入门

核心 45 分钟 跳到过关题
学习目标
  • 理解地址和 * / &
  • 会用指针改外部变量
  • 知道数组名和指针的关系

地址与取值

变量住在内存里,有门牌号(地址)。 &a 取 a 的地址; *p 表示「p 指向的那个东西」。

画一张图帮助理解:

内存示意
a 的地址假设是 0x100,里面存着 10。
执行 int *p = &a; 后,p 这个盒子(自己也有地址)里存的是 0x100。
*p 就是「按 0x100 找到 a」。所以 *p = 20 等于 a = 20。

指针类型 int * 里的 int 是在说:「我指向的东西应被当成 int 来读写」。

#include <stdio.h>

int main(void)
{
    int a = 10;
    int *p = &a;   /* p 存的是 a 的地址 */

    printf("%d\n", a);    /* 10 */
    printf("%d\n", *p);   /* 也是 10 */

    *p = 20;               /* 通过地址改 a */
    printf("%d\n", a);    /* 20 */
    return 0;
}

用指针当「输出参数」

这是函数「改外面变量」的标准做法。一个函数可以有多个「输出」,靠多个指针参数写出去:

#include <stdio.h>

void set_to_max(int a, int b, int *out)
{
    *out = (a > b) ? a : b;
}

int main(void)
{
    int result;
    set_to_max(3, 9, &result);  /* 把 result 的地址交给 out */
    printf("%d\n", result);  /* 9 */
    return 0;
}

数组和指针

数组名在表达式里常退化成「首元素地址」。a[i] 等价 *(a+i)。 所以 p = a; 后,p[0] 就是 a[0]。

注意:数组名本身不能被赋值成别的(a = p; 非法),它是「固定的首地址」。

int a[3] = {10, 20, 30};
int *p = a;
/* p[0]==10, *(p+1)==20, a[2]==30 */

必须小心

指针三大坑
  • 未初始化就使用(野指针):里面是随机地址,写它可能程序崩溃或改坏内存
  • 指向已销毁的局部变量(函数已返回,那块栈内存不再属于你)
  • 对 NULL 解引用:系统会直接崩(段错误)
入门原则:只使用你明确知道指向哪里的指针;可能为空就先 if (p != NULL)。

指针交换两个数

这是指针最经典、最必须会的用法:

#include <stdio.h>

void swap(int *a, int *b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

int main(void)
{
    int x = 3, y = 8;
    swap(&x, &y);
    printf("x=%d y=%d\n", x, y);  /* x=8 y=3 */
    return 0;
}

NULL:空指针

可以用 NULL 表示「暂时不指向任何人」。使用前先判断:

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

void print_if(int *p)
{
    if (p == NULL)
    {
        printf("空指针\n");
        return;
    }
    printf("%d\n", *p);
}

单片机场景 · 指针:通过地址修改变量

指针保存地址。先指向有效对象,再解引用读取或修改。

边界与验证:y 保存当时读到的值 5,不是对 v 的持续引用。*p = 9 后,v 为 9,y 仍为 5。

unsigned char v = 5;
unsigned char *p = &v;
unsigned char y = *p;
*p = 9;

本课过关

必做

&a 表示?

必做作业
  1. 写函数 void inc(int *x),让 *x 加 1。
  2. 解释 *p = 5 和 p = 5 的区别。
  3. 为什么 scanf 要写 &age 而不是 age?
先自己写完,再点开看解答

1:inc

void inc(int *x)
{
    *x = *x + 1;   /* 或 (*x)++ */
}

2:*p=5 与 p=5:*p=5 是「把 5 写进 p 指向的那格」;p=5 是「让 p 这个指针本身存地址 5」。后者几乎总是错的——地址 5 通常不是你的合法内存。

3:scanf 为何要 &age:scanf 需要知道「往哪里写」。age 只是数值,&age 才是盒子的地址。漏写 & 是 scanf 最常见的崩溃原因(未定义行为)。