指针是 C 语言最强大的特性,也是新手劝退率最高的知识点。其实它只做一件事:保存另一个变量的内存地址。把地址想成"门牌号",指针就是写着门牌号的小纸条——概念并不难,难的是习惯用它思考。

1. 内存与地址

程序运行时的每个变量都住在内存的某个"房间"里,房间有编号,就是地址。用取地址运算符 & 可以拿到变量的地址:

#include <stdio.h>

int main(void) {
    int x = 42;
    printf("x 的值: %d\n", x);
    printf("x 的地址: %p\n", (void *)&x);
    return 0;
}

%p 以十六进制打印地址,每次运行地址可能不同——这是正常的,地址由操作系统分配。

2. 指针的声明

声明指针用 类型 *名字,星号表示"这是一个指针",类型说明它指向什么类型的数据:

#include <stdio.h>

int main(void) {
    int x = 42;
    int *p = &x;      // p 保存 x 的地址
    printf("p 指向的地址: %p\n", (void *)p);
    return 0;
}

建议写作 int *p(星号靠变量),含义是 *p 是一个 int。

3. 解引用:通过地址访问数据

有了地址,可以用 *指针名 取出或修改它指向的变量,这叫解引用:

#include <stdio.h>

int main(void) {
    int x = 42;
    int *p = &x;
    printf("解引用 *p = %d\n", *p);   // 42

    *p = 100;        // 通过指针修改 x
    printf("修改后 x = %d\n", x);     // 100
    return 0;
}

注意区分:p 是地址,*p 是那个地址上的值,&x 是取地址——三者各司其职。

4. 指针与数组

数组名本身就是指向首元素的指针(常量指针),下标访问 arr[i] 等价于指针运算 *(arr + i):

#include <stdio.h>

int main(void) {
    int arr[] = {10, 20, 30};
    int *p = arr;             // 等价于 &arr[0]

    printf("arr[1] = %d\n", arr[1]);
    printf("*(p+1) = %d\n", *(p + 1));   // 也是 20
    printf("p[2] = %d\n", p[2]);         // 30,指针也能用下标
    return 0;
}

指针加 1 不是"地址 + 1 字节",而是跳到下一个同类型元素——对 int 来说地址加 4。

5. 指针参数:传址调用

前面学过函数参数是值传递,想修改外部变量必须传地址。经典的 swap 函数:

#include <stdio.h>

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

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

调用时传 &x&y(地址副本),函数内通过地址修改了原来的变量。

6. 常见陷阱

陷阱说明对策
未初始化指针指向随机地址,解引用必崩声明时初始化为 NULL
野指针指向已释放的内存free 后置 NULL
越界访问指针超出数组范围严格检查下标边界
类型不匹配int* 赋给 char*保持类型一致或显式转换
#include <stdio.h>

int main(void) {
    int *p = NULL;          // 好习惯:先置空
    if (p != NULL) {        // 使用前检查
        printf("%d\n", *p);
    } else {
        printf("指针为空,安全跳过\n");
    }
    return 0;
}
💡 学习建议:用纸笔画出"变量框"和"地址箭头",把本文每个例子的内存图都画一遍。指针学的是"图",不是"代码"。