☰
4-2 结构体指针
2026/10/17 2:41:57 网站建设 项目流程

4-2 结构体指针

⏱ 本节预计用时:45分钟

本节意义:上一讲的指针只会指一个int,可真实的数据是"一坨"——一个学生就有学号、姓名、三门成绩。这一节学会用指针指一整个结构体,看懂它在内存里怎么摆。

这对下一节、下下节做学生管理系统至关重要,因为里边全是这一讲的语法。C语言的真实开发就是这样的,结构体指针满天飞。

本节目标:会取结构体(以及它里面成员)的地址;会用->顺着地址读改数据;看懂结构体的内存布局和"结构体地址"的含义;能顺着地址访问结构体套结构体、结构体数组里的子数据。


1 真实的数据:一坨坨捆成结构体

4-1 里我们指过int x = 42;,一个格子、一个地址,挺香挺简单。

可真实的数据不长这样。看一个学生:

structStudent{intid;/* 学号 */charname[32];/* 姓名 */floatscores[3];/* 三门课成绩 */floattotal;/* 总分 */floataverage;/* 平均分 */};

一堆数据捆成的"一坨"。问题来了:

  • 怎么取"这整个学生"的地址?
  • 怎么取"他数学成绩"那一个格子的地址?
  • 顺着地址,怎么把某一门成绩读出来、改掉?

这三个问题,就是这一节要解决的事。


2 结构体指针示例

先看最小例子:

#include<stdio.h>structStudent{intid;floatscore;};intmain(){structStudentstu={1001,88};structStudent*p=&stu;/* p 记着 stu 的门牌号 */printf("%.0f\n",(*p).score);/* 麻烦写法 */printf("%.0f\n",p->score);/* 等价,-> 是简写 */p->score=100;/* 顺着地址改原件 */printf("%.0f\n",stu.score);/* 100 —— stu 真被改了 */return0;}

运行情况:

和 4-1 完全一个道理:&stu取门牌号,struct Student *p是装门牌号的格子,p->score就是"顺着门牌号进屋,看/改score那个格子"。

->是"指针取成员"的简写,完全等价于(*p).成员。看到->,就知道是指针在对结构体下手。


3 怎么取结构体里元素的地址

结构体里的每个成员,本身也是一个普通变量,一样能取地址。和之前一样,用&符号取地址 :

structStudentstu={1001,"张三",{85,92,78},0,0};&stu.id/* 学号那个格子的地址 */&stu.scores[1]/* 数学成绩那个格子的地址 */stu.name/* 数组名,本身就是地址(4-1 讲过) */&stu.scores/* 成绩数组的首地址 */

一条规律:要知道某个格子在哪,就在它前面加&。

注意stu.name前面不用加&——数组名本身就是首元素地址,这个坑 4-1 踩过了。


4 怎么根据地址读结构体里的数据

拿到地址,怎么把值读出来?还是 4-1 的老办法:*解引用。

structStudentstu={1001,"张三",{85,92,78},0,0};float*ps=&stu.scores[1];/* 拿到数学成绩的地址 */printf("%.0f\n",*ps);/* 92 —— 顺着地址读出来 */*ps=100;/* 顺着地址改掉 */printf("%.0f\n",stu.scores[1]);/* 100 —— 原件真变了 */

对付整个结构体,用结构体指针更省事:

structStudent*p=&stu;printf("%.0f\n",p->scores[1]);/* 顺着结构体地址去读scores[1],还是100 */p->scores[1]=32;/* 顺着结构体地址改成32 */printf("%.0f\n",p->scores[1]);/* 32 */printf("%.0f\n",*ps);/* 32 */printf("%.0f\n",stu.scores[1]);/* 32 */

取地址用&,读改数据用*或者->。指针就是取地址和用地址读数据两个操作,普通数据和结构体都一样。

把上面两段合成一个能跑的程序:

#include<stdio.h>structStudent{intid;charname[32];floatscores[3];floattotal;floataverage;};intmain(){structStudentstu={1001,"Li Lei",{85,92,78},0,0};/* 1. 顺着成员的地址读、改 */float*ps=&stu.scores[1];/* 取到数学成绩的地址 */printf("数学成绩:%.0f\n",*ps);*ps=100;printf("改完后:%.0f\n",stu.scores[1]);/* 2. 顺着结构体的地址读、改 */structStudent*p=&stu;/* 取到结构体的地址 */printf("%.0f\n",p->scores[1]);/* 顺着结构体地址去读scores[1],还是100 */p->scores[1]=32;/* 顺着结构体地址改成32 */printf("%.0f\n",p->scores[1]);/* 32 */printf("%.0f\n",*ps);/* 32 */printf("%.0f\n",stu.scores[1]);/* 32 */return0;}

运行情况:

数学成绩从 92 变成了 100,又变成了32。不管是成员的地址还是整个结构体的地址,顺着它都能读到、改到原变量。


5 结构体的内存布局

结构体在内存里不是散的,而是一整块连续的地,成员按声明顺序一个挨一个排下去。

拿我们系统里的Student举例(int4 字节、name[32]32 字节、三个float12 字节、total/average各 4 字节):

地址 内容 0x1000 ┌──────────────┐ │ id 4字节 │ 0x1004 ├──────────────┤ │ name 32字节 │ 0x1024 ├──────────────┤ │ scores 12字节 │ 0x1030 ├──────────────┤ │ total 4字节 │ 0x1034 ├──────────────┤ │ average 4字节 │ 0x1038 └──────────────┘

想知道实际长什么样?打印每个成员的地址,让它自己说话:

#include<stdio.h>structStudent{intid;charname[32];floatscores[3];floattotal;floataverage;};intmain(){structStudentstu={0};printf("结构体本身:%p\n",(void*)&stu);printf("id :%p\n",(void*)&stu.id);printf("name :%p\n",(void*)stu.name);printf("scores :%p\n",(void*)stu.scores);printf("total :%p\n",(void*)&stu.total);return0;}

运行情况:

你会看到:地址一串串往上加,每个成员的地址都比前一个靠后——因为它们就是一块连续的地上排着住。

内存对齐会让中间偶尔空出几格,先不用管,这不影响你理解"连续排布"。

结构体本身的地址,和它第一个成员的地址,是同一个数。

&stu==&stu.id/* 都是 0x1000 */

为什么会这样?因为结构体从第一个成员那儿"起头",整块地的门牌号自然就是第一间房子的门牌号。这和 4-1 里"数组名就是首元素地址"是一毛一样的道理。

所以:

结构体的地址 = 这块地的门牌号 = 第一个成员的地址。拿到它,就等于拿到了整块地,剩下的事只是"往里走几步"。

算第几个成员在哪,就是门牌号加上前面成员的字节数。当然,你不用自己算,写出类似于*(name+4)的代码去取id,编译器在p->id、stu.id里全帮你算好了!


6 结构体套结构体

还没有结束。

想不到吧,结构体里还能装结构体:

#include<stdio.h>structDate{intyear,month,day;};structStudent{intid;structDatebirthday;/* 结构体里套结构体 */};intmain(){structStudentstu={1001,{2008,5,20}};structStudent*p=&stu;printf("%d\n",stu.birthday.year);/* 2008 —— 一层层往里点 */printf("%d\n",p->birthday.year);/* 2008 —— 指针版:进里屋还能用点 */int*py=&p->birthday.year;/* 里屋的格子也能取地址 */printf("%d\n",*py);/* 2008 */return0;}

p是学生的门牌号,p->birthday是"里屋"这块地,p->birthday.year才是里屋里那个年份的格子。

运行情况:

我们下一讲要做的学生管理系统里也一样,StudentSystem结构体里装着Student结构体的数组,就是"结构体套结构体":

sys.students[2].id/* 第 2 个学生是个结构体,再点进它的 id */sys.students[2].scores[1]/* 第 2 个学生的数学成绩 */

8 结构体数组怎么遍历,访问里面的子元素

就三件事:取第 i 个的地址 → 用->读字段 → 循环。

for(inti=0;i<sys.count;i++){structStudent*s=&sys.students[i];/* 取第 i 个学生的地址 */printf("%d %s %.1f\n",s->id,s->name,s->total);}

拆开看:

  • sys.students[i]是第 i 个学生(一个结构体);
  • &sys.students[i]是它的门牌号,塞进Student *s;
  • s->id、s->name、s->total顺着地址取各个字段。

下一讲打印学生列表、按学号找人、算统计,全是这个套路。

完整示例:

#include<stdio.h>typedefstruct{intid;charname[32];floattotal;}Student;intmain(){Student list[3]={{1001,"Li Lei",255},{1002,"Han Meimei",276},{1003,"Wang Wu",240},};for(inti=0;i<3;i++){Student*s=&list[i];/* 取第 i 个的地址 */printf("第 %d 个:%d %s 总分 %.0f\n",i+1,s->id,s->name,s->total);}return0;}

运行情况:

系统里只是把list换成sys.students、把3换成sys.count,写法一模一样。


9 总结

  1. ->是结构体指针专用的取成员写法,等价于(*p).成员。
  2. 成员也能取地址:&stu.id、&stu.scores[1],加个&就行。
  3. 结构体是一块连续的内存,成员按声明顺序依次排布;结构体的地址 = 首成员的地址。
  4. 门牌号可以一层层往里走:p->birthday.year(结构体套结构体)、s = &sys.students[i]; s->id(结构体数组)。
  5. "取地址 +->+ 循环"在结构体数组里很常用。

下一讲,我们正式动手做学生成绩管理系统。

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

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

立即咨询