目标:
1. 理解结构体指针的使用
2. 掌握指针数组的概念
3. 学会用二级指针简单操作指针数组
(有一定跳跃性哦,请准备好)
二级指针与一级指针的区别
基本概念
承接上一篇对指针的解释——在阅读任何内容之前,先回忆指针,是一个什么形象?
一个盒子A,里面放着存储对象的地址。
如果这个盒子的地址被盒子B存储,那盒子B就是二级指针的概念。
- 一级指针:存储普通变量的内存地址,指向具体数据(如 int* p 指向 int 变量)。
- 二级指针:存储一级指针的内存地址,指向指针本身(如 int** pp 指向 int* 指针)。
内存模型
- 一级指针:直接关联到数据内存块,解引用(*p)获取数据值。
- 二级指针:间接关联数据,需两次解引用(**pp)获取数据值。
应用场景
- 一级指针:用于函数参数传递(提供地址修改外部变量)、动态数组操作(常见使用数组名)
- 二级指针:指针数组、修改指针本身(传递指针的地址来修改指针的值)。
代码示例
基础提要
第一个黑框是定义代码示例,当作基础知识补充(或是帮助大家回忆)。
/* 格式供说明使用*/
// 一级指针定义
int a = 10;
int* p = &a; // p 存储 a 的地址
// 二级指针定义
int** pp = &p; // pp 存储 p 的地址
//结构体定义
typedef struct Student{
int id;
int score;
} Student;
Student p1 = {001, 88}; // 创建一个结构体变量
Student* p = &p1; // p 指向该结构体变量
// 访问成员的两种方式:
p1.id // 通过结构体变量直接访问(点运算符)
p->id // 通过指针访问(箭头运算符),等价于 (*p).id
//理解指针数组
Student* p[3] = {&p1, &p2, &p3};
p[0]->score = 88;//这样修改结构体成员内的变量
/* 问:*(p + i) 和 p[i] 有什么区别?
没有区别。p[i] 会被编译成 *(p+ i),具体原因参考数组名“退化”成指针*/
例子
下面是一个学生成绩管理的小例子,用二级指针访问结构体变量的成员(ps:专门做练习,显得比较粗糙)
先解释为什么用二级指针:下面为Student结构体建立了一个指向三个结构体成员的指针数组,但是通过一级指针,只能访问这个指针指向的对象,多个就无从下手。只有依靠某个新的“指示”才能建立起这三个指针的索引,依次去找到这些一级指针、取出里面的结构体变量。因此这种场合下,需要二级指针发挥作用。
typedef struct Student{
int id;
int score;
}Student;//定义学生结构体;typedef: 使struct Student得以用Student代替
//函数声明
void print_person(Student* p);
void setScore(Student** p,int index,int new_score);
void showAll(Student** pps,int n);
int main()
{
Student p1 = {001,80};
Student p2 = {002,81};
Student p3 = {003,82};
Student* p[] = {&p1,&p2,&p3};//一个指针数组,p[0]到p[2]的值分别是结构体p1-p3的地址
//存放p[0]到p[2]的地址连续,但结构体p1-p3并不一定连续
//所以说p[0]-p[1]的内容并不连续
int len = sizeof(p)/sizeof(p[0]);//求有几个指针元素,传参数
int i = 0;
Student** pp = p;//二级指针pp 指向指针数组p。相当于用一个指针指向数组
showAll(pp,len);
setScore(pp,2,88);
i = 0;
showAll(pp,len);
return 0;
}
/**
* 显示所有学生信息
* @param pps 二级指针,指向指针数组的首元素
* @param n 要显示的学生个数
**/
void showAll(Student** pps,int n)
{
int i = 0;
while(i<n)
{
print_person(*(pps + i));//pps是二级指针,*pps是第一个元素 即p[0];
//在上文,p[0]是一个指针 p[0]–>p1
i++;
}
}
/**
* 修改指定学生的成绩
* @param pp 二级指针,指向指针数组
* @param index 学生索引(从0开始)
* @param new_score 新成绩
*
* 三种等价写法:
* 1. (*(pp + index))->score = new_score; // 指针运算
* 2. pp[index]->score = new_score; // 数组下标(推荐这种,不容易写绕)
* 3. (*(pp + index)).score = new_score; // 先用.再用*,不常用,不推荐
**/
void setScore(Student** pp,int index,int new_score)
{
(*(pp + index))->score = new_score;//效果同使用数组索引p[index]一样
//二级指针pp–>数组P的首元素地址 *pp–>取值,p[0]
}
/**
* 打印单个学生信息
* @param p 指向Student结构体的一级指针
**/
void print_person(Student* p)
{
printf("ID:%d\\tSCORE:%d\\n",p->id,p->score);//一级指针指向结构体,访问结构体成员用->
}
关系图解:
二级指针 pp=&p[0] 指针数组 p (连续8字节×3) 结构体变量 (不一定连续)
┌──────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ 0x7ff0 │──────────→ │ p[0] = 0x1000 │───────→ │ 0x1000: P1 │
│ &p[0] │ │ (指向P1) │ │ { id: 001, score:80 }│
└──────────┘ ├──────────────────┤ ├─────────────────────┤
│ p[1] = 0x2000 │───────→ │ 0x2000: P2 │
│ (指向P2) │ │ { id: 002, score:81 }│
├──────────────────┤ ├─────────────────────┤
│ p[2] = 0x3000 │───────→ │ 0x3000: P3 │
│ (指向P3) │ │ { id: 003, score:82 }│
└──────────────────┘ └─────────────────────┘
常见误区
- 混淆解引用层级:二级指针需两次 * 操作才能访问数据。
- 错误修改更高一级指针的值 :比如下面这个错误,指向的空间错了,自然找不到目标值。
/*函数setScore给了大家一个错误示范*/
void setScore(Student** pp,int index,int new_score)
{
*pp = *pp + index; /* 错误1:指针运算错误。参见结构体存储空间的解释:
因为不连续,而pp–>p[0],那么*pp为p[0],p[0]+index;背景为p[0]实际上是指针
所以这个指针的确偏移了,但并不是偏移到结构体的正确地址,所以这段代码会出错*/
(*pp)->score = new_score; /*错误2:在上面的基础上,*pp被修改了,这里指向的空间是错误的,
}
写给读者的话
回顾一下本文涉及的三个关键:
1. 指针数组存的是地址,本身连续存储
2. 二级指针指向指针数组,用来管理多个指针
3. pp[i] 和 *(pp+i) 是等价的,但前者更直观、更方便
恭喜恭喜!理解了这些 = 迈过了指针进阶的一道坎。
当然,这只是指针应用的冰山一角,我们可以继续思考、带着问题去学习,进步会更快的!💪
有欠妥、错误的地方,欢迎大家批评指正。





