内容正文:
C语言程序设计
电子工业出版社
9.4节:结构体指针
理解结构体指针的概念,掌握其定义与使用方
熟练运用->运算符访问结构体成员
能够运用结构体指针编写程序,实现对结构体的高效操作
能够分析和调试包含结构体指针的C语言程序
培养严谨的逻辑思维和程序设计能力
养成良好的编程习惯和代码规范意识
知识目标
能力目标
素养目标
01
02
03
学习目标
目录
Part 01
教学导入
Part 02
知识讲授
Part 03
师生互动
Part 04
课堂练习
Part 05
课堂小结
教学导入:温故知新
回顾:指针的本质
指针是一个特殊的变量,它的值存储的不是普通数据,而是**另一个变量的内存地址**。
在之前的课程中,我们已经掌握了指向基础数据类型(如int,float,char)的指针用法。
🤔 思考题:
如果一个变量的结构本身非常复杂(例如一个包含姓名、学号、成绩的学生信息),也就是一个“结构体”,我们该如何定义指针去指向它?
回顾:结构体的本质
结构体是一种**用户自定义的复合数据类型**,它打破了单一类型的限制,允许将多个不同类型的数据“打包”在一起,形成一个整体。
struct Student {
long num; // 学号
char name[20]; // 姓名
char sex; // 性别
float score; // 成绩
};
💡 关键提示:结构体变量在内存中占据一块连续的存储空间。
教学导入:问题引入
📝 场景模拟
假设处理一个包含1000个学生信息的数组,传统做法是传递整个结构体:
void printStudent(struct Student s)
{ printf("%ld,%s,%c, %f
",s.num,s.name,s.sex,s.score);}
这样做有什么缺点?
每次调用函数,都需要复制整个结构体的数据到新的内存空间。
当结构体很大(如包含多个数组、字符串),或者要处理的数组元素数量很多时,会产生巨大的内存开销,并浪费大量CPU时间在数据拷贝上,导致程序效率低下。
解决方案
只需要将结构体变量的地址传递给函数。
函数直接通过地址访问和操作原数据,避免了任何不必要的数据复制,节省内存且效率极高。
今天的主角:
结构体指针
教学导入:课程导航
本节课我们将深入学习结构体与指针的结合应用,掌握两大核心场景:
01指向结构体变量的指针
• 如何定义一个指向结构体变量的指针?
• 如何通过指针访问结构体的成员?
• 核心运算符`->`的使用与优先级解析
02指向结构体数组的指针
• 如何使用指针高效遍历结构体数组?
• 指针在结构体数组中移动的步长逻辑
• 开发中的常见易错点与调试注意事项
知识讲授
结构体指针
指向结构体变量的指针
指向结构体数组的指针
第一部分
指向结构体变量的指针
知识讲授
知识讲授:结构体指针的定义
什么是结构体指针?
它是一个指向结构体变量的指针,其核心作用是存储结构体变量在计算机内存中的**起始地址**,通过它可以间接访问结构体变量中的数据。
标准语法格式
在定义指针变量时,必须明确指出它指向的结构体类型:
struct 结构体名 *指针变量名;
💻 代码示例
// 1. 先定义结构体类型
struct Student { long num; char name[20]; float score; };
// 2. 再定义结构体变量
struct Student stu_1;
// 3. 最后定义指向该类型的指针
struct Student *p;
⚠️ 核心要点
指针变量的类型,必须与它要指向的**结构体变量类型**完全一致,就像一把钥匙只能开一把锁,不能混用。
知识讲授:让指针指向结构体变量
核心操作
使用 C 语言的取地址运算符 &,将结构体变量在内存中的起始地址,直接赋值给同类型的结构体指针变量。
内存逻辑
指针变量p中存储了stu_1的起始地址。建立联系后,我们就能通过指针p间接访问并操作stu_1的所有成员数据。
程序文本【9.10】指向结构体变量的指针
struct student
{ int num;
char ename[20];
char sex;
int age;
float score;
char addr[30]; } ;
struct student stu_1=
{1,"cui lin",'M',19,86.4,"shanghai"};
struct student *p=&stu_1;
知识讲授:访问结构体成员的三种方式
前置假设:struct Student stu_1 = {10101, "Li Lin", 'M', 89.5};
struct Student *p = &stu_1;
方式一:直接访问 (使用 . 运算符)
💡 语法规则:
结构体变量名 . 成员名
📝 代码示例:
printf("Name: %s
", stu_1.name);
方式二:间接访问 (使用 (*指针).成员名)
语法:(*指针变量名).成员名
解释:p 表示指针 p 指向的变量 stu_1,再用“.”访问成员。
⚠️ 注意:
p 两侧的括号不可省略!因为“.”的优先级高于“*”。
示例:printf("Name: %s
", (*p).name);
知识讲授:访问结构体成员的第三种方式(重点)
方式三:间接访问 (使用 -> 运算符)
核心语法:指针变量名 -> 成员名
这是C语言为指针访问结构体成员提供的“专用语法糖”。它同时结合了取值运算 (*)和直接成员访问 (.)的双重功能,简化书写。
p->name ≡ (*p).name
printf("Name: %s
", p->name);
为什么推荐使用 ->?
代码更简洁,可读性更高
相比需要频繁使用括号的 (*p).name,p->name 的书写更顺手,一眼就能看出是通过指针访问成员,语义清晰。
避免复杂嵌套的括号混乱
在处理“指向结构体的指针的指针”(二级指针)等复杂场景时,-> 能极大减少括号数量,降低代码出错率。
知识讲授:变量成员形式用以下三种形式表示等价
程序文本【9.10】指向结构体变量的指针
struct student
{ int num;
char ename[20];
char sex;
int age;
float score;
char addr[30]; } ;
struct student stu_1=
{1,"cui lin",'M',19,86.4,"shanghai"……};
struct student *p=&stu_1;
知识讲授:三种方式的等价性总结
假设有定义:struct Student stu_1; struct Student *p = &stu_1;,以下三种访问结构体成员的方式完全等价:
stu_1.num
直接通过结构体变量名访问
(*p).num
先对指针解引用,再用点号访问
(注意:圆括号不可省略!)
p -> num
通过指针直接访问(最推荐写法)
可读性高,书写更简洁
💡 核心记忆口诀
1.变量用点(.),指针用箭头(->)—— 分清“变量”与“指针”身份。
2. 如果想用指针配合点号,一定要记得加星号和括号((*p).) —— 养成良好的代码习惯。
背诵记忆
01. 定义
struct 结构体名 *指针名;
声明一个指向结构体类型的指针变量,用于存储结构体变量的地址。
02. 赋值
指针名 = &结构体变量名;
使用取地址运算符 `&` 获取结构体变量的地址,并赋值给对应的指针变量。
03. 访问
• 结构体变量访问成员:
. (点运算符)
• 结构体指针访问成员:
-> (箭头运算符)
💡 等价关系:
指针->成员 ≡ (*指针).成员
程序文本:指向结构体指针变量的定义、输入和输出
#include <stdio.h>
struct student
{ char xh[3], xm[10];
int cj;
}s ={"10","王强",85 };
main( )
{ struct student *p;
p= &s;
printf(" 学号 姓名 成绩
");
printf("%7s%7s%7d
",s.xh,s.xm,s.cj );
printf("%7s%7s%7d
",(*p).xh,(*p).xm,(*p).cj );
printf("%7s%7s%7d
",p->xh,p->xm,p->cj ); }
例题分析 1
例题分析 1:指向结构体指针变量的定义、输入和输出
例题分析 2
程序文本:通过指向结构体变量的指针变量输出结构体变量中成员的信息
#include <stdio.h>
#include <string.h>
int main()
{struct student
{ long num; char name[20]; char sex; float score; };
struct student stu_1; // 定义struct student类型的变量stu_1
struct student * p; // 定义指向struct student 类型数据的指针变量p
p=&stu_1; // p指向stu_1
stu_1.num=10101; // 对结构体变量的成员赋值
strcpy(stu_1.name,"Li Lin");
stu_1.sex='M';
stu_1.score=89.5;
printf("No.:%ld
name:%s
sex:%c
score:%5.1f
",stu_1.num,stu_1.name,stu_1.sex,stu_1.score);
printf("
No.:%ld
name:%s
sex:%c
score:%5.1f
",(*p).num,(*p).name,(*p).sex, (*p).score); }
例题分析 2
课堂问答
Q1. 指针访问结构体成员
struct Person { int age; char name[10];}
person1, *p = &person1;
请问以下哪个语句可以正确地将 person1 的年龄设置为 20?
A. p.age = 20; B. *p.age = 20;
C. (*p).age = 20; D. p->age = 20;
正确答案:C、D
Q2. 语法对比与推荐
`(*p).name` 和 `p->name` 是否完全等价?
为什么推荐在代码中使用后者?
是完全等价的。
推荐使用->的原因:
1.代码更简洁:少了一对括号,视觉干扰更少。
2.可读性更高:一眼就能识别是指针访问,尤其在多层嵌套的复杂结构体指针中优势极其明显。
课堂练习 1
📝 练习题目
定义一个结构体Point,包含两个整型成员x和y。编写程序,定义一个 `Point` 类型的变量 `p1` 并初始化其坐标为 (3, 4)。
然后定义一个指向 `Point` 的指针 `pp`,使其指向 `p1`。最后,通过指针 `pp` 将 `p1` 的坐标修改为 (5, 6),并分别使用`.`、`(*pp).` 和 `pp->`三种方式输出修改后的坐标。
💡 提示:
理解坐标点在内存中的表示,以及如何通过不同的语法访问结构体成员。
课堂练习 1
程序文本
#include<stdio.h>
struct Point{
int x, y;
};
int main() {
struct Point p1 = {3, 4};
struct Point *pp = &p1; // 指向结构体变量
pp->x = 5; pp->y = 6; // 修改值
printf("Using . : (%d, %d)
", p1.x, p1.y);
printf("Using (*pp).:(%d, %d)
",(*pp).x,(*pp).y);
printf("Using -> : (%d, %d)
", pp->x, pp->y);
return 0;
}
课堂练习 1
课堂练习 2
📝 编程任务
任务要求:
1. 定义一个结构体Book,包含三个成员:书名 `title`、作者 `author`、价格 `price`。
2. 声明一个 `Book` 类型的变量 `book1`,并定义一个指向它的指针变量。
3.仅通过指针为 `book1` 赋值:书名 "C Programming",作者 "K&R",价格 58.5。
4.仅通过指针将书籍的完整信息打印输出。
课堂练习 2
程序文本
#include<stdio.h>
#include<string.h>
struct Book
{ char title[50];
char author[30];
float price; };
int main() {
struct Book book1;
struct Book *p = &book1;
// 指针赋值: 注意字符串用 strcpy
strcpy(p->title,"C Programming");
strcpy(p->author,"K&R"); p->price =58.5;
printf("Title: %s
Price: %.2f
", p->title, p->price);
return 0; }
课堂练习 2
第二部分
知识讲授
指向结构体数组的指针
知识讲授:指向结构体数组的指针
01 / 定义与赋值
struct Student stu[3] = { ... };
struct Student *p;
p = stu; // 将数组首元素地址赋给指针 p
// 等价于:p = &stu[0];
💡 核心原理:
数组名 `stu` 是一个指向数组第一个元素的**常量指针**。因此,直接将 `stu` 赋值给指针变量 `p`,就实现了指针与数组的关联,这是C语言中最常用的写法。
02 / 内存映射示意图
📍 映射逻辑:
• `p` ➔ 指向数组第 1 个元素 `stu[0]`|• `p+1` ➔ 指向数组第 2 个元素 `stu[1]`
• `p+i` ➔ 指向数组第 i+1 个元素 `stu[i]`
知识讲授:指针的移动
`p++` 的含义
当指针 `p` 指向一个结构体数组的元素时,执行 `p++` 操作,指针的值并不会简单地加 1,而是会增加一个结构体的整体长度。
举个例子:假设一个 `struct Student` 类型的元素占用 32 字节,那么执行 `p++` 后,指针 `p` 的值会直接增加 32,从而精准地指向数组中的下一个元素。
遍历数组的实现
for(p = stu; p < stu + 3; p++) {
// 访问指针指向的当前元素
printf("%s
", p->name);
}
利用指针移动的特性,我们可以在循环中直接通过指针的自增操作,轻松地遍历整个结构体数组,比使用数组下标访问更高效、更简洁。
知识讲授:易错点讲解
典型误区 · 错误用法
p = stu[0].name;// 错误!类型不匹配
❓ 为什么会报错?
•stu[0].name是一个char *类型,指向字符串的第一个字符。
•指针 p被定义为struct Student *类型,用于指向结构体。
不同类型的指针不能直接赋值,这是初学者最容易混淆的地方。
核心概念 · 正确理解
结构体指针p的核心职责是指向一个完整的结构体对象,而非对象中的某一个成员变量。
💡 如果强行赋值,必须进行强制类型转换,但这通常会破坏程序逻辑,且无法通过指针访问结构体的其他成员。
例题分析:指向结构体数组的指针
程序文本【9.11】指向结构体数组的指针
#include<stdio.h>
#include<string.h>
struct student
{ int num; char ename[20];
char sex; int age;
float score; char addr[30];
};
struct student stu[3]={{1,"Cui Lin",'M',19,86.4,"Shanghai"},
{2,"Zhou Li",'F',19,76.6,"Nanjing"},{3,"Chengyan",'F',18,92.5,"Nanchang"}};
main()
{ struct student *p; //定义指向结构体数组的指针
p=stu; //指针p指向结构体数组首地址
for(p=stu;p<stu+3;p++)
printf("%d,%s,%c,%d,%.2f,%s
",p->num,p->ename,p->sex,p->age,p->score,p->addr); }
例题分析:指向结构体数组的指针
例题分析:指向结构体数组的指针
课堂问答
Q1:循环边界辨析
在例9.11中,如果将for循环的条件改为p <= stu + 2,程序还能正确运行吗?为什么?
答案:能。因为stu + 2正好是最后一个元素stu[2]的地址。条件p <= stu + 2和原来的p < stu + 3的效果是完全一致的,都能遍历数组所有元素。
Q2:指针与数组的等价写法
假设有代码片段:
struct Student stu[5], *p = stu;
请问:表达式(p+2)等价于什么?(p+2)->num等价于什么?
答案:根据指针运算规则:
1.(p+2)等价于stu[2](指向数组的第3个元素)
2.(p+2)->num等价于stu[2].num(访问该元素的 num 成员)
背诵记忆
01
数组名即地址
结构体数组名代表数组首元素的地址,本质是一个指向结构体类型的指针常量。
02
指针赋值
执行`p = stu;`操作,即可使指针变量 `p` 指向结构体数组 `stu` 的第一个元素。
03
指针移动
执行`p++`时,指针将向后移动一个结构体长度,指向数组的下一个完整元素。
04
访问元素成员
使用`p->member`或`(*p).member`两种方式,访问指针当前指向元素的指定成员。
05
核心禁忌
严禁将结构体成员的地址直接赋值给结构体类型指针,类型不匹配会导致逻辑错误。
课堂练习:寻找高薪员工
📝 编程挑战:寻找高薪员工
1. 定义一个结构体Employee,包含id (工号)、name (姓名)、salary (工资)三个字段。
2. 创建并初始化一个包含 4 个员工信息的结构体数组。
3. 使用结构体指针遍历数组,找出薪资最高的员工。
4. 打印出该员工的完整个人信息。
课堂练习:寻找高薪员工
程序文本
#include<stdio.h>
struct Employee
{ int id;
char name[20];
float salary; };
int main() {
struct Employee emp[4] = {{101,"Alice",8500}, {102,"Bob",9200},{103,"Job",9000},{104,"Kelly",19200}};
struct Employee *p, *max_emp = emp; // 初始化指针
for(p = emp; p < emp+4; p++)
if(p->salary > max_emp->salary)
max_emp = p;
printf("最高薪员工: %s (ID: %d), 薪资: %.2f
",max_emp->name,max_emp->id,max_emp->salary);
}
课堂练习:寻找高薪员工
课堂练习:冒泡排序
📝 题目要求
使用结构体指针,将一个结构体数组中的元素,按照结构体内部的 `num`(学号)字段,从小到大进行排序。
课堂练习:冒泡排序
程序文本
#include<stdio.h>
struct Student
{ int num; char name[20]; };
int main() {
struct Student stu[3] = {{103, "Tom"}, {101, "Jerry"}, {102, "Mike"}};
struct Student *p, *q, temp;
printf("排序前:
"); // 输出:排序前的结构体数组
for (p = stu; p < stu + 3; p++)
printf("学号:%d\t姓名:%s
", p->num, p->name);
for(p = stu; p < stu + 2; p++) // 核心:指针操作结构体进行冒泡排序与交换
for(q = p+1; q < stu+3; q++)
if(p->num > q->num) { temp = *p; *p = *q; *q = temp; }
printf("
排序后:
");// 输出:排序后的结构体数组
for (p = stu; p < stu + 3; p++) printf("学号:%d\t姓名:%s
", p->num, p->name); }
课堂练习:冒泡排序
课堂小结
指向结构体变量的指针
掌握结构体指针的定义与赋值方法,重点理解并区分通过 `->` 和 `(*p).` 两种方式来访问结构体的成员变量。
指向结构体数组的指针
学会利用指针遍历结构体数组,深入理解指针 `p++` 的移动逻辑,即移动一个“结构体元素”所占的字节数,实现高效的数据遍历。
💡 核心思想
指针本质是一种“间接访问”内存的手段。对于复杂的结构体类型,直接传递指针比复制整个结构体更节省内存开销,同时让程序逻辑更灵活、执行效率更高。
🚩 核心原则
“变量用点,指针用箭头”
区分访问方式:结构体变量名使用 `.`,指针变量使用 `->`
课堂小结
课后任务
01 基础题
编写一个程序,定义一个表示矩形的结构体Rectangle,包含width和height。
定义一个结构体变量,并使用指针计算并输出该矩形的周长和面积。
02 进阶题
定义结构体Car,包含品牌(brand)、型号(model)和价格(price)。
创建含5个汽车信息的结构体数组并初始化。
用结构体指针遍历数组,找出价格最低的汽车并打印信息。
03 综合题
定义结构体Student,包含学号(num)、姓名(name)和两门课程成绩(score[2])。
创建含3个学生信息的结构体数组并初始化。
用结构体指针完成:
1. 计算并输出每个学生的平均分;
2. 按平均分对数组降序排序;
3. 输出排序后的完整信息。
谢谢观看
THANKS
$