内容正文:
C语言程序设计
电子工业出版社
9.5节:结构体与函数
掌握结构体传参的三种方式
理解值传递与地址传递的区别
能根据需求选择合适的传参方式
能编写结构体与函数的综合程序
培养模块化、结构化的编程思想
提升处理复杂数据类型的逻辑能力
知识目标
能力目标
素养目标
01
01
01
学习目标
目录
Part 01
教学导入
Part 02
知识讲授
Part 03
师生互动
Part 04
课堂练习
Part 05
课堂小结
教学导入:思考一个问题
01 / 场景引入
想象一下,现在我们要在程序代码中,完整地描述生活中常见的一样物品——“一本书”。
这不是一个简单的数字或字母,它包含了很多特征。我们应该从哪里入手?
02 / 关键信息拆解
●书名 / 作者:(字符串 String)
●价格:(浮点数 Float,如 59.80)
●页数/出版年份:(整数 Integer)
思考:这些数据的类型完全不同,如何高效关联它们?
03 / 遇到的挑战
如果每本书都创建5个独立的变量来存储这些数据:
→ 当需要描述多本书时,变量名会变得极其冗长,管理混乱。
解决方案:寻找一种将不同类型数据“打包”的机制。
教学导入:引出核心概念 —— 结构体 (struct)
01 / 解决方案
C语言提供了一种强大的自定义数据类型 ——结构体。它打破了单一数据类型的限制,为复杂信息的存储与管理提供了完美的解决思路。
02 / 核心定义
结构体是一种构造类型,它允许我们将多个不同类型的变量(称为“成员”)组合在一起,形成一个单一的、有名字的实体,实现数据的“打包”管理。
它就像一个“数据容器”或一张“信息卡片”,将零散、不同维度的信息有条理地整合在一处。
03 / 直观类比
教学导入:本节课目标
书籍信息卡片 (结构体示例)
📖 书名:《Python编程入门》
✍️ 作者:张三 著
📅 出版:2024年
📐 规格:320 页 | 定价 ¥59.0
💡 思考:如何用编程语言将这些分散的数据整合为一个“信息单元”?
本节课 · 核心目标
核心任务:结构体与函数的交互
学习如何将结构体作为参数传递给函数,实现代码的模块化与高效复用,避免重复代码。
🎯 探索三种传递方式:
① 传递结构体成员(基本数据类型传递)
② 传递结构体变量(整体值传递,值拷贝)
③ 传递结构体指针(地址传递,最高效)
知识讲授
结构体与函数
用结构体变量/结构体变量成员作参数
用指向结构体变量/数组的指针作参数
第一部分
用结构体变量/结构体变量成员作参数
知识讲授
知识讲授:用结构体变量的成员作参数
01 / 核心思想
将结构体变量的单个成员,像普通变量(如 int、float)一样,直接传递给函数处理。
02 / 本质机制
典型的“值传递”。函数接收原成员数据的独立副本,而非数据本身。
03 / 主要特点
✅ 简单直观:仅处理个别成员,易读性高。
⚠️数据隔离:修改副本不影响原变量。
实参 形参
“结构体成员”是 简单变量 同类型简单变量
变量的地址 指针变量
数组 数组名或指针
指向数组的指针
例题分析:示例程序
题目描述
定义一个结构体Student,包含学生姓名和成绩。编写一个函数print_score,接收一个float类型的参数,打印该成绩。在主函数中定义一个结构体变量stu,并将其score成员作为参数传递给print_score函数。
解题思路拆解
1.定义结构体:
定义包含name和score的结构体Student。
2.定义函数:
定义print_score函数,参数为float s,打印s的值。并在print_score函数中修改s的值。
3.主函数调用与输出:
初始化stu变量,调用函数传递stu.score;调用结束后输出stu.score的值,观察形参是否影响实参。
例题分析:示例程序
程序文本
#include <stdio.h>
struct Student
{char name[20];float score;}; //定义结构体
void print_score(float s) //形参为同类型简单变量
{printf("Score: %.2f
", s); //输出形参的值
s+=2; //尝试改变形参的值
}
main()
{struct Student stu={"Zhang San",95.5}; // 将结构体成员stu.score作为参数传递
print_score(stu.score); //调用 print_score函数,并将结构体变量成员作参数
printf("Score: %.2f
",stu.score); //在主调函数中验证实参是否改变
}
例题分析:示例程序
例题分析:示例程序【9.12】分析
题目描述
设学生的3门课成绩已经保存在一个结构体数组中,要求用子程序计算并返回3门课的总分。
在主程序main()中先定义一个结构体类型,再创建一个结构体数组,并将此数组的成员score数组传递到子程序add()中,由add()计算总分并返回。
解题思路拆解
1.定义结构体:定义包含姓名、3门成绩和总分的结构体stu
2.创建数组:在main中创建结构体数组student[3]
3.输入数据:循环输入3个学生的姓名与成绩
4.调用函数:调用add函数计算总分并赋值给total
5.输出结果:循环输出所有学生的姓名、成绩与总分
例题分析:示例程序【9.12】分析
程序文本【9.12】
#include<stdio.h>
main()
{ struct stu { char name[80];int score[3];int total;};
struct stu student[3]; int i;
printf("please input three stu'name and score for the three stu:
");
for(i=0;i<3;i++)
{ scanf("%s",student[i].name);
scanf("%d%d%d",&student[i].score[0],&student[i].score[1],&student[i].score[2]),
student[i].total=add(student[i].score[0],student[i].score[1],student[i].score[2]);}
printf("the score table of the three students is:
");
for(i=0;i<3;i++)
printf("%10s %d %d %d %d
",student[i].name,student[i].score[0],
student[i].score[1],student[i].score[2],student[i].total);}
int add(int x,int y,int z)
{return x+y+z;}
例题分析:示例程序【9.12】分析
知识讲授:用结构体变量作参数
核心思想
本质机制
特点与权衡
直接将整个结构体变量作为实参传递给形参,如同移交完整“信息档案”。
仍是“值传递”,形参是实参的副本,开辟新内存拷贝所有成员值,二者独立。
✅ 便捷安全:直接访问成员,形参修改不影响实参。
⚠️ 性能损耗:大结构体拷贝耗时耗内存。
实参 形参
用结构体变量作参数 结构体变量名 结构体变量名
结构体变量地址 同类型结构体类型的指针
同类型结构体类型的指针 同类型结构体类型的指针
例题分析:示例程序
题目描述
定义一个结构体Student,包含学生姓名和成绩。编写一个函数print_student,接收一个struct Student类型的参数,打印学生信息并尝试修改其成绩。
在主函数中定义一个结构体变量stu,并将其作为参数传递给print_student函数,观察主函数中stu的值是否被改变。
解题思路拆解
1.定义结构体:定义包含 name 和 score 的结构体 Student。
2.定义函数:编写 print_student 函数,参数为 struct Student s,打印信息并尝试修改 s.score。
3.主函数调用:在 main 中初始化 stu 并作为实参传递给函数。
4.验证结果:主函数中打印 stu.score,观察是否被修改。
例题分析:示例程序
程序文本
#include<stdio.h>
struct Student // 定义结构体类型
{ char name[20];
float score;};
void print_student(struct Student s) //形参为结构体变量名
{ printf("Name: %s
", s.name);
printf("Score: %.2f
", s.score);
s.score = 100.0;// 尝试修改形参 s 的值
}
int main() {
struct Student stu = {"Li Si", 88.0};
print_student(stu); //函数调用 ,实参为结构体变量名
printf("Score in main: %.2f
", stu.score);//验证主函数中的值是否被改变
return 0; }
例题分析:示例程序
例题分析:示例程序【9.13】分析
题目描述
定义一个嵌套结构体,外层结构体student包含学生姓名、性别、年龄和一个score类型的成员;内层结构体score包含三门课成绩。
编写一个函数total,接收一个struct student类型的参数,计算并返回该学生的总分。
在主函数中创建一个包含两个学生的结构体数组,输入数据后调用total函数计算并打印每个学生的总分。
解题思路拆解
1.定义嵌套结构体:先定义内层结构体 score,再定义外层结构体 student。
2.定义函数:定义 total 函数,参数为 struct student,计算三门课总分并返回。
3.主函数调用:创建结构体数组 s[2],循环输入学生信息。
4.计算与输出:循环调用 total 函数,打印每个学生总分。
例题分析:示例程序【9.13】分析
程序文本【9.13】
struct score { int score1; int score2; int score3;};
struct student { char name[20]; char sex; int age; struct score stscore; };
int total(struct student stud);
main()
{ int i; struct student s[2];
for(i=0;i<2;i++)
{ printf("please input name and scores
");
scanf("%s",s[i].name);
scanf("%d",&s[i].stscore.score1);
scanf("%d",&s[i].stscore.score2);
scanf("%d",&s[i].stscore.score3);}
for(i=0;i<2;i++) printf("%s:Total Score is %d
",s[i].name,total(s[i]));}
int total(struct student stud)
{ return(stud.stscore.score1+stud.stscore.score2+stud.stscore.score3);}
例题分析:示例程序【9.13】分析
背诵记忆
成员作为参数
结构体成员作为函数参数时,遵循值传递的规则。
本质上与普通变量(如 int、float)的传参方式没有区别。
变量整体传参
结构体变量作为函数参数时,同样遵循值传递规则。
系统会为形参创建一个完整的数据拷贝,若结构体很大,会带来一定的内存开销。
关键特性
在值传递的机制下,函数内部对形参的任何修改,都不会影响到主调函数中的实参。
数据隔离,安全性高,但无法直接通过函数修改外部数据。
课堂问答
提问 01/ 函数传参的本质
如果想通过函数修改主函数中结构体变量的成员值,使用本次课程学的两种方法可以实现吗?为什么?
💡 答案:不能。因为这两种方法都是“值传递”。函数在被调用时,会在栈上复制一份实参的副本,函数内部操作的只是这个副本,因此无法影响到原始的结构体数据。
提问 02/ 性能与效率考量
当结构体非常庞大时(例如包含数百个成员),使用“传递结构体变量”的方式会有什么潜在问题?
⚠️ 答案:运行效率低下。传递整个结构体需要将大量数据从实参复制到形参,这一过程会显著消耗CPU的运算时间以及宝贵的内存(栈)空间,拖慢程序的运行速度。
课堂练习
题目 01:计算矩形面积
1. 定义一个表示矩形的结构体Rectangle,包含成员变量length(长) 和width(宽)。
2. 编写一个函数calculate_area,接收一个Rectangle结构体变量作为参数,计算并返回其面积。
3. 在主函数main中定义一个矩形变量,为其赋值,并调用函数计算并输出面积。
几何示意:面积 = 长 × 宽 (Area = length × width)
课堂练习
程序文本:计算矩形面积
#include<stdio.h>
struct Rectangle
{ float length; float width; }; //定义结构体
float calculate_area(struct Rectangle rect)//形参为结构体变量名
{return rect.length * rect.width; }
int main()
{ struct Rectangle r = {5.0, 3.0};
float area = calculate_area(r);//实参为结构体变量名
printf("The area of the rectangle is:%.2f
",area);
return 0; }
课堂练习
课堂练习
题目02:编写学生信息打印函数
1. 定义Student结构体(含name, id, score)。
2. 编写print_student_info函数,接收结构体变量并打印信息。
3. 在main函数中创建变量并调用打印函数。
解题思路解析
•结构体定义:明确学生属性字段。
•函数设计:形参为结构体类型,遍历成员输出。
•主函数调用:初始化实例并传参调用。
课堂练习
程序文本:
#include <stdio.h>
struct Student //定义Student结构体
{ char name[20]; // 学生姓名
int id; // 学号
float score; }; // 成绩
void print_student_info(struct Student stu)
//编写print_student_info函数,接收结构体变量并打印信息
{ printf("学生姓名:%s
", stu.name);
printf("学生学号:%d
", stu.id);
printf("学生成绩:%.2f
", stu.score); }
main() {
struct Student student1={"张三",2023001,92.5f};//在main函数中创建结构体变量并初始化
print_student_info(student1); }// 调用打印函数
课堂练习
第二部分
知识讲授
用指向结构体变量/数组的指针作参数
知识讲授:用指向结构体的指针作参数
01 核心思想
将结构体变量的地址(即指向结构体的指针)传递给函数,而非传递整个结构体的数据副本。
“不送东西,只送东西在哪的位置”
02 底层本质
这是一种典型的地址传递 。
函数形参接收的不是实参的值,而是实参在内存中的物理地址。函数内部通过该地址直接操作内存中的原始数据。
03 关键特点
✅ 效率极高:仅传递4/8字节地址,避免大量拷贝。
✏️可修改原值:直接操作实参,突破“值传递”的限制。
💻 语法细节:访问成员时需使用 ->操作符。
知识讲授:地址传递 vs 值传递
值传递
• 传递的是数据的副本,函数操作的是这份“复印件”。
• 在函数内部对参数的任何修改,都不会影响外部的原始数据。
• 适用场景:数据量较小,或逻辑上需要严格隔离、保护原始数据不被修改时。
地址传递
• 传递的是数据的内存地址,函数通过地址直接操作原始数据。
• 效率更高,常用于需修改原始数据或处理大对象/数组的场景。
例题分析
题目描述
使用指针作为函数参数,编写一个程序,对一个学生的成绩进行修改。
核心目标:理解如何利用指针传递结构体,直接修改主函数中的原始数据,而非传递副本。
01
定义结构体
定义Student结构体,包含姓名、学号和成绩(score)字段。
02
定义更新函数
定义update_score函数,参数为指向结构体的指针struct Student *p和新成绩值。
03
修改成绩数据
在函数内部,使用指针操作符->修改内容:p->score = new_score;
04
主函数调用
定义结构体变量stu,调用函数时传入变量的地址:update_score(&stu, 95);
例题分析
程序文本
// 定义学生结构体
#include<stdio.h>
struct Student {
char name[20]; // 姓名
float score; // 成绩
};
void update_score(struct Student *p, float new_score) //形参为指向结构体变量的指针
{ p->score = new_score; //指针访问成员,并修改成员值
printf("Updated: %.2f
", p->score); //指针访问成员输出成员值
}
int main() {
struct Student stu = {"Wang Wu", 75.0};
update_score(&stu, 90.0);//传递地址,实参为结构体变量的地址
printf("In main: %.2f
", stu.score);//再次输出结构体变量中的成员值,验证形参是否改变实参
return 0; }
例题分析
例题分析:示例程序【9.14】分析
题目描述
定义一个包含5个学生信息的结构体数组boy,每个学生包含学号、姓名、性别和成绩。编写一个函数ave,接收一个指向结构体的指针,计算学生的平均分和不及格人数。在主函数中调用该函数并输出结果。
解题思路拆解
1.定义结构体:定义struct stu,含num, name, sex, score成员。
2.定义函数:ave函数接收struct stu *指针ps,遍历计算总分和不及格人数。
3.主函数调用:定义指针ps指向数组boy,调用ave(ps)。
例题分析:示例程序【9.14】分析
程序文本【9.14】计算一组学生的平均成绩和不及格人数。
struct stu
{ int num; char *name; char sex;
float score;}boy[5]={{101,"Li ping",'M',45},{102,"zhang ping",'M',62.5},
{103,"He fang",'F',92.5},{104,"Cheng ling",'F',87},{105,"Wang ming",'M',58}};
main()
{ struct stu *ps;
void ave(struct stu *ps); //函数声明
ps=boy;
ave(ps);} //实参为指向结构体变量的指针
void ave(struct stu *ps) //定义函数,形参为指向结构体变量的指针
{ int c=0,i; float ave,s=0;
for(i=0;i<5;i++,ps++)
{ s+=ps->score; if(ps->score<60) c+=1;}
printf("s=%f
",s);ave=s/5;
printf("average=%f
count=%d
",ave,c);}
例题分析:示例程序【9.14】分析
背诵记忆
01
本质是“地址传递”
传递指向结构体的指针是地址传递,这意味着函数内部对指针的操作会直接映射到原始数据,从而实现对数据的修改。
02
指针访问成员语法
当使用指针访问结构体成员时,不能使用“.”号,必须使用->箭头操作符,标准格式为:指针名->成员名。
03
结构体数组的传递
数组名在作为函数参数时,会自动退化为指向数组首元素的指针。因此,向函数传递一个结构体数组,本质上也是一种地址传递。
04
工程最佳实践
在实际开发中,为了提升程序运行效率(减少拷贝开销)或直接修改原数据,当结构体体积较大,或明确需要修改内容时,应优先使用指针传递。
课堂问答
课堂问答 01
提问:`p->score` 和 `(*p).score` 有什么关系?
答案:它们是完全等价的,都表示访问指针 `p` 指向的结构体中的 `score` 成员。在代码编写中,`->` 其实是 `(*p).` 的语法糖,也是一种更简洁、更常用的简写形式。
课堂问答 02
提问:若函数参数为 `struct Student s[]`,在函数内部能执行 `s++` 吗?为什么?
答案:不可以。虽然数组名作为参数传递时会“退化”为指针,但它本质上是一个常量指针,其指向的地址是固定的,不允许被修改。`s++` 试图修改指针本身的值,这会导致编译报错。
课堂练习
题目 01 · 员工薪资管理
1.定义结构体:设计一个名为Employee的结构体,用于存储员工信息,需包含三个字段:工号 (id)、姓名 (name) 和 工资 (salary)。
2.编写加薪函数:实现一个函数raise_salary,它接收一个Employee类型的指针和一个加薪百分比,通过指针修改员工的实际工资数值。
3.测试运行:在main函数中,实例化一个员工对象并调用加薪函数,验证结果是否正确。
核心考点提示:
这道题考察的是“地址传递”的概念。只有将结构体的指针传入函数,才能直接修改原结构体的内部数值,而不是操作一个临时的副本。
课堂练习
程序文本
#include<stdio.h>
struct Employee
{ int id; char name[20]; float salary; };
void raise_salary(struct Employee *e,float percent)
{ e->salary = e->salary * (1 + percent / 100); }
int main()
{ struct Employee emp={1001,"John Doe",5000.0};
printf("Before raise: %.2f
", emp.salary);
raise_salary(&emp, 10.0); //函数调用
printf("After raise: %.2f
", emp.salary);
return 0; }
课堂练习
课堂练习
题目 02 | 结构体与指针应用
定义一个结构体`Point`表示平面上的点,包含坐标属性 `x` 和 `y`。
编写一个函数`distance`,接收两个 `Point` 类型的指针作为参数,计算并返回这两点之间的直线距离。
公式:sqrt( (x₂ - x₁)² + (y₂ - y₁)² )
课堂练习
程序文本
#include<stdio.h>
#include<math.h>
struct Point{ float x, y; };
float distance(struct Point *p1,struct Point *p2) {
float dx = p2->x - p1->x, dy = p2->y - p1->y;
return sqrt(dx*dx + dy*dy); }
int main() {
struct Point p1 = {-5,5}, p2 = {3,-3};
printf("Distance: %.2f
",distance(&p1,&p2));
return 0; }
课堂练习
课堂小结
01 传递成员
参数形式:func(stu.member)
传递内容:成员值的副本
修改原数据:❌ 不能
执行效率:✅ 高
适用场景:只需访问结构体中的单个成员时
02 传递变量
参数形式:func(stu)
传递内容:整个结构体的副本
修改原数据:❌ 不能
执行效率:⚠️ 低 (结构大时)
适用场景:只需读取数据,且结构体较小时
03 传递指针
参数形式:func(&stu)
传递内容:结构体的内存地址
修改原数据:✅ 能
执行效率:✅ 高
适用场景:需修改内容,或数据量大时
💡 核心原则:需要修改数据 / 追求效率 → 用指针; 只需读取数据 → 用变量或成员
课堂小结
课后任务
01基础题
定义一个表示“日期”的结构体Date,包含成员变量year, month, day。
编写一个函数is_leap_year,接收一个Date结构体作为参数,判断该结构体中的年份是否为闰年,并返回一个值,在主函数中通过接收的值输出是否为闰年。
02进阶题
定义一个表示“图书”的结构体Book,包含成员变量:书名title、作者author、价格price和页数pages。
创建一个长度为5的Book结构体数组并初始化。编写函数find_book_by_author,根据输入的作者名,查找并打印出该作者的所有书籍信息。
03挑战题
设计一个程序,实现对小型公司员工信息的管理系统。员工信息需包含姓名、工号、部门、职位及薪资。
要求至少实现以下核心功能:
1.添加新员工信息
2.查询并打印所有员工信息
3.修改指定工号员工的薪资
提示:建议将功能封装为独立函数。
谢谢观看
THANKS
$