🚚 一、结构体传参的两种方式
在 C 语言中,向函数传递结构体有两种方式:传值调用(传递副本)和传址调用(传递地址)。两者的核心区别在于是否复制数据,适用场景不同。
二、传值调用:传递结构体副本 🔄
核心原理
- 复制整个结构体:函数接收的是原结构体的副本,在内存中在额外开辟一块空间,对副本的修改不会影响外部变量。
- 效率:结构体较大时,复制数据会消耗更多内存和时间。
🌰 示例
#include <stdio.h>
#include <string.h>
// 定义学生结构体
typedef struct {
char name[20]; // 姓名
int age; // 年龄
float score; // 成绩
} Student;
// 传值调用:参数是结构体变量(副本)
void print_student_value(Student stu) {
// 访问成员用 . 操作符
printf("传值调用:姓名 %s,年龄 %d,成绩 %.1f\n",
stu.name, stu.age, stu.score);
// 修改副本的成绩(不影响外部变量)
stu.score = 90.0;
}
int main() {
Student tom = {"Tom", 18, 85.5};
// 传值调用:传递tom的副本
print_student_value(tom);
// 检查原数据是否被修改
printf("原数据成绩:%.1f\n", tom.score); // 输出85.5(未改变)
return 0;
}
🔍 代码解析
- 定义结构体:用
typedef
简化类型名,方便后续使用。 - 函数参数:
Student stu
是结构体变量,函数内部操作的是副本。 - 成员访问:通过
stu.name
直接访问成员(.
操作符用于结构体变量)。 - 数据隔离:修改副本的
score
不影响主函数中的tom
。
三、传址调用:传递结构体地址 🔗
核心原理
- 传递结构体地址:函数接收的是结构体的地址(指针),直接操作原数据,无需复制,不在额外开辟内存空间,使用的是形参的地址。
- 效率:只传递 4/8 字节的地址(取决于平台),效率更高,适合大结构体。
🌰 示例
#include <stdio.h>
#include <string.h>
typedef struct {
char name[20];
int age;
float score;
} Student;
// 传址调用:参数是结构体指针
void print_student_ptr(Student* stu_ptr) {
// 访问成员用 -> 操作符(等价于 (*stu_ptr).name)
printf("传址调用:姓名 %s,年龄 %d,成绩 %.1f\n",
stu_ptr->name, stu_ptr->age, stu_ptr->score);
// 修改原数据的成绩(直接影响外部变量)
stu_ptr->score = 90.0;
}
int main() {
Student tom = {"Tom", 18, 85.5};
// 传址调用:传递tom的地址(&tom)
print_student_ptr(&tom);
// 检查原数据是否被修改
printf("原数据成绩:%.1f\n", tom.score); // 输出90.0(已改变)
return 0;
}
🔍 代码解析
- 参数类型:
Student* stu_ptr
是结构体指针,接收&tom
的地址。 - 成员访问:通过
stu_ptr->name
访问成员(->
操作符用于指针)。 - 直接修改原数据:函数内修改
score
会影响主函数中的tom
。
四、传值 🆚 传址:优缺点
特性 | 传值调用 | 传址调用 |
---|---|---|
数据安全 | 原数据不会被修改(副本机制) | 原数据可能被修改(需谨慎操作) |
内存占用 | 复制整个结构体(空间开销大) | 仅传递地址(4/8 字节,开销极小) |
效率 | 低(尤其大结构体) | 高(无需复制数据) |
适用场景 | 结构体小,或需要保护原数据时 | 结构体大,或需要修改原数据时 |
语法 | 直接传递结构体变量(如stu ) | 传递地址(如&stu ),函数内用-> |
五、传参易错点总结 🚫
1. 传址调用时漏写 &
或 ->
// ❌ 错误:传址调用未取地址
print_student_ptr(tom); // 应该是 &tom
// ❌ 错误:指针访问成员用 . 而非 ->
stu_ptr.name = "Alice"; // 应该是 stu_ptr->name
2. 传值调用修改数据无效
void wrong_modify(Student stu) {
stu.age = 20; // 改的是副本,原数据不变
}
3. 传址调用时指针未初始化(野指针)
void dangerous_call(Student* ptr) {
ptr->age = 18; // 若ptr是野指针,程序崩溃
}
六、我推荐传址调用的 3 大理由 ✅
- 效率优先:无需复制大结构体,尤其当结构体包含数组、字符串等数据时,效率提升明显。
- 直接修改原数据:适合需要在函数中修改结构体成员的场景(如初始化、数据更新)。
- 节省内存:地址大小固定(32 位 4 字节,64 位 8 字节),无论结构体多大,传址都更省空间。
七、何时用传值调用? 🤔
- 结构体非常小(如仅包含几个基本类型),复制开销可忽略。
- 需要保护原数据:不希望函数内部修改外部结构体时(如只读操作)。
🌟 代码:两种传参方式对比
#include <stdio.h>
typedef struct { int x, y; } Point;
// 传值调用:返回副本的和
int add_value(Point p) {
p.x += 10; // 改副本,不影响原数据
return p.x + p.y;
}
// 传址调用:直接修改原数据的x
void add_ptr(Point* p, int num) {
p->x += num; // 直接改原数据
}
int main() {
Point p = {1, 2};
printf("传值调用结果:%d\n", add_value(p)); // 输出13(副本的x=11,y=2)
printf("原数据x:%d\n", p.x); // 输出1(未改变)
add_ptr(&p, 10); // 传址调用,x变为11
printf("传址调用后x:%d\n", p.x); // 输出11(已改变)
return 0;
}
总结:传参方式选择口诀
- 小结构体用传值:简单、安全,不怕数据被改。
- 大结构体用传址:高效、省内存,修改直接生效。
- 指针传参加 const:若不想修改原数据,加
const
保护:void read_only(const Student* stu) { /* 此时不能修改stu->成员 */ }
通过清晰的代码示例和对比,快速掌握结构体传参的核心逻辑。
记住:传址调用是 C 语言处理大结构体的 “高效武器”,但要注意指针的合法性检查,避免野指针风险~ 💪