4-2 结构体指针
4-2 结构体指针⏱ 本节预计用时45分钟本节意义上一讲的指针只会指一个int可真实的数据是一坨——一个学生就有学号、姓名、三门成绩。这一节学会用指针指一整个结构体看懂它在内存里怎么摆。这对下一节、下下节做学生管理系统至关重要因为里边全是这一讲的语法。C语言的真实开发就是这样的结构体指针满天飞。本节目标会取结构体以及它里面成员的地址会用-顺着地址读改数据看懂结构体的内存布局和结构体地址的含义能顺着地址访问结构体套结构体、结构体数组里的子数据。1 真实的数据一坨坨捆成结构体4-1 里我们指过int x 42;一个格子、一个地址挺香挺简单。可真实的数据不长这样。看一个学生structStudent{intid;/* 学号 */charname[32];/* 姓名 */floatscores[3];/* 三门课成绩 */floattotal;/* 总分 */floataverage;/* 平均分 */};一堆数据捆成的一坨。问题来了怎么取这整个学生的地址怎么取他数学成绩那一个格子的地址顺着地址怎么把某一门成绩读出来、改掉这三个问题就是这一节要解决的事。2 结构体指针示例先看最小例子#includestdio.hstructStudent{intid;floatscore;};intmain(){structStudentstu{1001,88};structStudent*pstu;/* p 记着 stu 的门牌号 */printf(%.0f\n,(*p).score);/* 麻烦写法 */printf(%.0f\n,p-score);/* 等价- 是简写 */p-score100;/* 顺着地址改原件 */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*psstu.scores[1];/* 拿到数学成绩的地址 */printf(%.0f\n,*ps);/* 92 —— 顺着地址读出来 */*ps100;/* 顺着地址改掉 */printf(%.0f\n,stu.scores[1]);/* 100 —— 原件真变了 */对付整个结构体用结构体指针更省事structStudent*pstu;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 */取地址用读改数据用*或者-。指针就是取地址和用地址读数据两个操作普通数据和结构体都一样。把上面两段合成一个能跑的程序#includestdio.hstructStudent{intid;charname[32];floatscores[3];floattotal;floataverage;};intmain(){structStudentstu{1001,Li Lei,{85,92,78},0,0};/* 1. 顺着成员的地址读、改 */float*psstu.scores[1];/* 取到数学成绩的地址 */printf(数学成绩%.0f\n,*ps);*ps100;printf(改完后%.0f\n,stu.scores[1]);/* 2. 顺着结构体的地址读、改 */structStudent*pstu;/* 取到结构体的地址 */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 └──────────────┘想知道实际长什么样打印每个成员的地址让它自己说话#includestdio.hstructStudent{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;}运行情况你会看到地址一串串往上加每个成员的地址都比前一个靠后——因为它们就是一块连续的地上排着住。内存对齐会让中间偶尔空出几格先不用管这不影响你理解连续排布。结构体本身的地址和它第一个成员的地址是同一个数。stustu.id/* 都是 0x1000 */为什么会这样因为结构体从第一个成员那儿起头整块地的门牌号自然就是第一间房子的门牌号。这和 4-1 里数组名就是首元素地址是一毛一样的道理。所以结构体的地址 这块地的门牌号 第一个成员的地址。拿到它就等于拿到了整块地剩下的事只是往里走几步。算第几个成员在哪就是门牌号加上前面成员的字节数。当然你不用自己算写出类似于*(name4)的代码去取id编译器在p-id、stu.id里全帮你算好了6 结构体套结构体还没有结束。想不到吧结构体里还能装结构体#includestdio.hstructDate{intyear,month,day;};structStudent{intid;structDatebirthday;/* 结构体里套结构体 */};intmain(){structStudentstu{1001,{2008,5,20}};structStudent*pstu;printf(%d\n,stu.birthday.year);/* 2008 —— 一层层往里点 */printf(%d\n,p-birthday.year);/* 2008 —— 指针版进里屋还能用点 */int*pyp-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(inti0;isys.count;i){structStudent*ssys.students[i];/* 取第 i 个学生的地址 */printf(%d %s %.1f\n,s-id,s-name,s-total);}拆开看sys.students[i]是第 i 个学生一个结构体sys.students[i]是它的门牌号塞进Student *ss-id、s-name、s-total顺着地址取各个字段。下一讲打印学生列表、按学号找人、算统计全是这个套路。完整示例#includestdio.htypedefstruct{intid;charname[32];floattotal;}Student;intmain(){Student list[3]{{1001,Li Lei,255},{1002,Han Meimei,276},{1003,Wang Wu,240},};for(inti0;i3;i){Student*slist[i];/* 取第 i 个的地址 */printf(第 %d 个%d %s 总分 %.0f\n,i1,s-id,s-name,s-total);}return0;}运行情况系统里只是把list换成sys.students、把3换成sys.count写法一模一样。9 总结-是结构体指针专用的取成员写法等价于(*p).成员。成员也能取地址stu.id、stu.scores[1]加个就行。结构体是一块连续的内存成员按声明顺序依次排布结构体的地址 首成员的地址。门牌号可以一层层往里走p-birthday.year结构体套结构体、s sys.students[i]; s-id结构体数组。取地址 - 循环在结构体数组里很常用。下一讲我们正式动手做学生成绩管理系统。