结构体传参:传值 vs 传址

🚚 一、结构体传参的两种方式 

在 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;
}

🔍 代码解析

  1. 定义结构体:用typedef简化类型名,方便后续使用。
  2. 函数参数Student stu是结构体变量,函数内部操作的是副本。
  3. 成员访问:通过stu.name直接访问成员(.操作符用于结构体变量)。
  4. 数据隔离:修改副本的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;
}

🔍 代码解析

  1. 参数类型Student* stu_ptr是结构体指针,接收&tom的地址。
  2. 成员访问:通过stu_ptr->name访问成员(->操作符用于指针)。
  3. 直接修改原数据:函数内修改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 大理由 ✅

  1. 效率优先:无需复制大结构体,尤其当结构体包含数组、字符串等数据时,效率提升明显。
  2. 直接修改原数据:适合需要在函数中修改结构体成员的场景(如初始化、数据更新)。
  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 语言处理大结构体的 “高效武器”,但要注意指针的合法性检查,避免野指针风险~ 💪

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值