C语言指针的三个进阶形态
2026/8/25 3:55:11 网站建设 项目流程

C语言指针的三种进阶形态

概念写法本质类比
指向指针的指针(二层指针)int **pp一个变量,里面存的是另一个指针变量的地址“门牌号本子的存放抽屉号”
指针数组int *arr[10]一个数组,里面每个元素都是指针“一排门牌号本子”
函数指针void (*f)(int)一个变量,里面存的是函数的入口地址“记录修鞋铺位置的纸条”

1.int **pp(指向指针的指针)

你有一张纸条(p),上面写着"101室"(&a)。
你又拿了一张纸条(pp),上面写着"放p的那个抽屉的编号"。
所以*pp取出的是p(101室),**pp取出的是a的值。

int a = 5; int *p = &a; // p存的是a的地址 int **pp = &p; // pp存的是p的地址 printf("%p\n", *pp);//输出p的地址 printf("%d\n", **pp); // 输出5

嵌入式里什么时候用

  • 函数要修改外部指针本身(如分配内存后改变指针指向)。
  • 操作二维动态数组(行指针数组)。
//函数要修改外部指针指针示例: //让函数 alloc_memory 把 malloc 申请到的内存地址“带出来”,赋给外部的指针 p void alloc_memory(int **pp) { *pp = malloc(100); // 改变外部指针 p 本身 } int *p = NULL; alloc_memory(&p); // 现在 p 指向了堆上 100 字节 //操作二位动态数组示例: //用二层指针 matrix 创建一个“动态二维数组”,即行数和列数都在运行时由 rows 和 cols 决定, //而不是编译时固定。 int **matrix = malloc(rows * sizeof(int*)); for (int i = 0; i < rows; i++) { matrix[i] = malloc(cols * sizeof(int)); }

2.int *arr[3](指针数组)

指针数组的目的:用一个数组统一管理多个地址,方便批量操作这些地址指向的数据

这是一个数组,里面有3个元素,每个元素都是int*类型(指向int的指针)。

int a=1, b=2, c=3; int *arr[3] = {&a, &b, &c}; printf("%d", *arr[0]); // 输出1
int main(int argc, char *argv[])
  • argc:参数个数。

  • argv这是一个指针数组,每个元素指向一个字符串(命令行输入的内容)。
    例如输入./a.out hello world,则:

    • argv[0]"./a.out"

    • argv[1]"hello"

    • argv[2]"world"

这就是典型的指针数组用法:操作系统把命令行字符串的起始地址,全部放进这个数组里传给你

使用场景:

场景例子本质
字符串数组char *argv[] = {"ls", "-l", NULL};每个元素指向一个字符串
函数跳转表void (*cmd[])(void) = {func1, func2, func3};用下标直接调用不同函数
多对象管理int *bufs[4];每个指向不同传感器数据缓冲区用循环统一处理所有对象

与二层指针对比:

写法本质内存典型场景
int *arr[10]数组,里面存了10个地址40字节(10个指针)固定数量指针,编译时确定
int **pp指针,指向一个指针变量4或8字节(1个指针)需要动态分配行数(二维数组)或函数内修改外部指针

指针数组 = 多个指针的“收纳盒”,让你能用循环一次性处理所有地址。


3.void (*f)(int)(函数指针)

定义一个变量f,它指向一个“参数为int、返回值为void”的函数。

void print_num(int n) { printf("%d", n); } void (*f)(int) = print_num; // f指向print_num函数 f(5); // 调用,输出5

嵌入式里什么时候用

  • 中断向量表:每个中断号对应一个函数地址。

  • 回调函数:比如定时器溢出时,调用你指定的函数。

  • 状态机跳转表:根据当前状态自动执行对应函数。

4.总结:

写法类型占内存取内容后得到
int *p;指向int的指针4字节(地址)int
int **pp;指向“指向int的指针”的指针4字节int *
int *arr[10];有10个元素的数组,每个元素是int*40字节(4×10)int值(经过两次解引用)
void (*f)(int);指向函数的指针4字节(地址)函数入口地址(调用即执行)

题目
请用C语言写一个程序,实现以下功能:

  1. 定义三个整数a=10, b=20, c=30

  2. 定义一个指针数组arr,让它的三个元素分别指向a, b, c

  3. 定义一个指向指针的指针pp,让它指向arr的第二个元素(即指向b的那个元素)。

  4. 定义一个函数指针f,指向一个函数print_value,该函数接收一个int*指针,并打印它指向的值。

  5. pp取出b的值,并用f打印它。

#include <stdio.h> void print_value(int *p) { printf("value = %d\n", *p); } int main() { int a = 10, b = 20, c = 30; // 1. 指针数组:每个元素指向一个int int *arr[3] = {&a, &b, &c}; // 2. 指向指针的指针:指向arr的第2个元素(即&b) int **pp = &arr[1]; // 或者 &arr[1] 等价于 arr + 1 // 3. 函数指针 void (*f)(int*) = print_value; // 4. 通过pp取出b的值,用f打印 f(*pp); // *pp = arr[1] = &b,所以 f(&b) 打印20 return 0; }

补充:三层指针

1、“一层指针”是什么

int a = 5; int *p = &a;
  • a是一个房间,里面住了5,门牌号是0x1000

  • p是一个“门牌号本子”,上面写着0x1000

一层指针 = 存地址的变量


2、二层指针:存“门牌号本子”的“抽屉号”

int **pp = &p;
  • pp是一个“抽屉”,里面放的不是门牌号,而是“存放门牌号本子的抽屉号”

  • 也就是说:pp指向的是p这个变量本身。

画出来就是:

pp (0x2000) → p (0x1000) → a (5)
  • pp的值是0x2000(p 的地址)

  • *pp取出的是p的值,即0x1000

  • **pp取出的是a的值,即5


3、什么时候必须用二层指针?(核心场景)

场景1:函数里要改变“指针变量本身”

如果你写:

void set_null(int *p) { p = NULL; // 改的是 p 的副本,外部的 p 不受影响 }

传进去的p是复制品,改它没用。
要让函数把外面的指针改成NULL,必须传指针的地址

void set_null(int **pp) { *pp = NULL; // 直接改外部的指针变量 } int *p = &a; set_null(&p); // 传的是 p 的地址 // 现在 p 变成 NULL 了

本质:你要改的是“本子上的内容”,但本子本身是别人的,所以你只能把“本子的存放位置”告诉函数,让它去那里改。


场景2:动态分配二维数组

int **matrix; matrix = (int **)malloc(rows * sizeof(int *)); for (int i = 0; i < rows; i++) { matrix[i] = (int *)malloc(cols * sizeof(int)); }
  • matrix是一层指针,指向一个“指针数组”的首地址。

  • 每个matrix[i]又是一个指针,指向一行数据。

  • 访问matrix[1][2]时,编译器先找第1行指针,再找第2列数据。


场景3:操作系统命令行参数

int main(int argc, char **argv)
  • argv是一个二层指针(等价于char *argv[])。

  • 它指向一个指针数组,数组里每个元素指向一个命令行字符串。

  • 操作系统通过二层指针,把多个字符串的地址一次性传给你。


4、三层指针有没有?

有,但极少见。比如:

int ***ppp;
  • ppp指向pppp指向pp指向a

  • 只有在三维动态数组或者非常深的嵌套修改场景才会用到,普通工程基本碰不到。


5、总结

一层指针存地址,二层指针存“存地址的变量”的地址。
需要修改指针本身时(如函数内改外部的指针、动态分配二维数组),就用二层指针。


6、题目

:为什么swap_ptr的参数必须是int **,而不能是int *

void swap_ptr(int **pp1, int **pp2) { int *temp = *pp1; *pp1 = *pp2; *pp2 = temp; } int a=1, b=2; int *p1 = &a, *p2 = &b; swap_ptr(&p1, &p2); // 现在 p1 指向 b,p2 指向 a


:因为要交换的是p1p2这两个指针变量本身,而不是它们指向的值。传int *只能交换复制品,传int **才能直接改原件。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询