C语言struct赋值是直接拷贝还是地址操作?

99ANYc3cd6
预计阅读时长 22 分钟
位置: 首页 C语言 正文

在 C 语言中,可以直接使用赋值运算符 对两个相同类型的结构体变量进行赋值,这种赋值是“深拷贝” (Deep Copy),它会将源结构体中所有成员的值逐个复制到目标结构体中。

c语言 struct的赋值
(图片来源网络,侵删)

基本赋值(最常见的情况)

当两个结构体变量具有完全相同的类型(即定义时使用同一个 struct 标签)时,可以直接使用 进行赋值。

示例代码

#include <stdio.h>
// 1. 定义一个结构体类型 Person
struct Person {
    char name[50];
    int age;
    float height;
};
int main() {
    // 2. 定义并初始化一个结构体变量 p1
    struct Person p1 = {"Alice", 30, 1.75f};
    // 3. 定义另一个同类型的结构体变量 p2
    struct Person p2;
    // 4. 使用赋值运算符将 p1 的值赋给 p2
    // 这行代码会执行“深拷贝”:
    // p2.name <-- p1.name (复制整个字符串)
    // p2.age  <-- p1.age  (复制整数值)
    // p2.height <-- p1.height (复制浮点数值)
    p2 = p1;
    // 5. 验证赋值结果
    printf("p1 的信息:\n");
    printf("  姓名: %s, 年龄: %d, 身高: %.2f\n", p1.name, p1.age, p1.height);
    printf("\np2 的信息:\n");
    printf("  姓名: %s, 年龄: %d, 身高: %.2f\n", p2.name, p2.age, p2.height);
    // 6. 修改 p2,看看是否会影响 p1
    p2.age = 99;
    strcpy(p2.name, "Bob");
    printf("\n修改 p2 后:\n");
    printf("p1 的信息: 姓名: %s, 年龄: %d, 身高: %.2f\n", p1.name, p1.age, p1.height);
    printf("p2 的信息: 姓名: %s, 年龄: %d, 身高: %.2f\n", p2.name, p2.age, p2.height);
    return 0;
}

输出结果

p1 的信息:
  姓名: Alice, 年龄: 30, 身高: 1.75
p2 的信息:
  姓名: Alice, 年龄: 30, 身高: 1.75
修改 p2 后:
p1 的信息: 姓名: Alice, 年龄: 30, 身高: 1.75
p2 的信息: 姓名: Bob, 年龄: 99, 身高: 1.75

分析:

  • p2 = p1; 成功地将 p1 的所有数据复制到了 p2
  • 修改 p2 的成员后,p1 的成员没有改变,这证明了它们是两个独立的变量,拥有各自独立的数据副本,这正是“深拷贝”的特点。

结构体数组赋值

结构体数组的赋值遵循同样的规则,你可以使用循环将一个结构体数组的每个元素逐个赋值给另一个结构体数组的对应元素。

示例代码

#include <stdio.h>
#include <string.h>
struct Point {
    int x;
    int y;
};
int main() {
    struct Point points1[3] = {{1, 2}, {3, 4}, {5, 6}};
    struct Point points2[3];
    // 不能直接用 points2 = points1; 来赋值整个数组!
    // 必须通过循环逐个元素赋值
    for (int i = 0; i < 3; i++) {
        points2[i] = points1[i];
    }
    // 验证
    printf("points2 的内容:\n");
    for (int i = 0; i < 3; i++) {
        printf("点 %d: (%d, %d)\n", i, points2[i].x, points2[i].y);
    }
    return 0;
}

结构体指针赋值

当涉及到结构体指针时,情况就完全不同了,指针赋值只是复制指针的地址,而不是结构体的内容,这被称为“浅拷贝” (Shallow Copy)

c语言 struct的赋值
(图片来源网络,侵删)

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student {
    char name[50];
    int id;
};
int main() {
    // 1. 在堆上为 s1 分配内存
    struct Student *s1 = (struct Student*)malloc(sizeof(struct Student));
    if (s1 == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    strcpy(s1->name, "Charlie");
    s1->id = 101;
    // 2. 定义另一个指针 s2
    struct Student *s2;
    // 3. 将 s1 的地址赋给 s2 (浅拷贝)
    // s2 和 s1 指向了同一块内存地址
    s2 = s1;
    // 4. 通过 s2 修改数据
    printf("修改前 s1 的信息: %s, %d\n", s1->name, s1->id);
    s2->id = 202;
    strcpy(s2->name, "David");
    // 5. 观察 s1 的数据是否被改变
    // 因为它们指向同一块内存,s1 的数据也被改变了
    printf("修改后 s1 的信息: %s, %d\n", s1->name, s1->id);
    // 6. 释放内存
    // 只需要释放一次,因为 s1 和 s2 指向的是同一块内存
    // free(s1); free(s2); 会导致 "double free" 错误
    free(s1);
    // s2 现在是一个“悬垂指针”(dangling pointer),使用它会导致未定义行为
    s1 = NULL; // 好的编程习惯,将指针置为 NULL
    return 0;
}

输出结果

修改前 s1 的信息: Charlie, 101
修改后 s1 的信息: David, 202

分析:

  • s2 = s1; 并没有创建一个新的 Student 对象,只是让 s2 指针指向了 s1 已经指向的内存。
  • 通过 s2 修改数据,实际上是修改了同一块内存中的数据,s1 看到的数据也变了。
  • 重要提示:当两个指针指向同一块动态分配的内存时,只能 free 一次,否则会导致程序崩溃(双重释放错误)。

结构体作为函数参数

结构体作为函数参数时,默认情况下是“值传递” (Pass-by-Value),这意味着函数内部会创建一个结构体参数的完整副本,在函数内部修改这个副本不会影响到函数外部的原始结构体。

示例代码

#include <stdio.h>
struct Rectangle {
    int width;
    int height;
};
// 函数接收一个 Rectangle 结构体,并修改它
// 这里的 r 是 main 函数中 rect 的一个副本
void double_size(struct Rectangle r) {
    r.width *= 2;
    r.height *= 2;
    printf("在 double_size 函数内部:\n");
    printf("  宽度: %d, 高度: %d\n", r.width, r.height);
}
int main() {
    struct Rectangle rect = {10, 20};
    printf("调用函数前:\n");
    printf("  宽度: %d, 高度: %d\n", rect.width, rect.height);
    double_size(rect); // 传递的是 rect 的副本
    printf("调用函数后:\n");
    printf("  宽度: %d, 高度: %d\n", rect.width, rect.height);
    return 0;
}

输出结果

调用函数前:
  宽度: 10, 高度: 20
在 double_size 函数内部:
  宽度: 20, 高度: 40
调用函数后:
  宽度: 10, 高度: 20

分析:

  • double_size 函数内部的 rrect 的一个拷贝。
  • 函数对 r 的修改不会影响 main 函数中的原始 rect

高级用法:结构体指针作为函数参数

为了提高效率(特别是当结构体很大时),通常使用结构体指针作为函数参数,这样传递的只是一个地址(通常为 4 或 8 字节),而不是整个结构体的拷贝,在函数内部,可以通过指针来修改原始结构体的内容。

c语言 struct的赋值
(图片来源网络,侵删)

示例代码

#include <stdio.h>
struct Rectangle {
    int width;
    int height;
};
// 函数接收一个 Rectangle 结构体的指针
void double_size_ptr(struct Rectangle *r) {
    // 通过指针访问成员需要使用 ->
    r->width *= 2;
    r->height *= 2;
}
int main() {
    struct Rectangle rect = {10, 20};
    printf("调用函数前:\n");
    printf("  宽度: %d, 高度: %d\n", rect.width, rect.height);
    double_size_ptr(&rect); // 传递 rect 的地址
    printf("调用函数后:\n");
    printf("  宽度: %d, 高度: %d\n", rect.width, rect.height);
    return 0;
}

输出结果

调用函数前:
  宽度: 10, 高度: 20
调用函数后:
  宽度: 20, 高度: 40

分析:

  • 我们传递的是 rect 的地址 &rect
  • double_size_ptr 函数通过这个地址直接操作原始的 rect 变量,因此修改会生效。

总结与最佳实践

场景 操作 类型 说明
变量赋值 struct B = struct A; 深拷贝 创建 A 的完整副本给 B,两者独立。
指针赋值 struct B_ptr = A_ptr; 浅拷贝 两个指针指向同一块内存,修改一个会影响另一个,注意 free 次数。
函数参数(值) func(struct A) 深拷贝 函数内部得到 A 的副本,修改不影响外部。
函数参数(指针) func(&A) 浅拷贝 函数通过指针直接操作 A,修改会影响外部,效率更高,尤其对大结构体。

最佳实践建议:

  1. 赋值:当需要将一个结构体的所有内容复制到另一个同类型变量时,直接使用 运算符,这是最简单、最清晰的方式。
  2. 函数传参:对于小结构体,直接按值传递比较简单安全,对于大结构体,强烈建议按指针传递,以避免巨大的栈开销和性能损失。
  3. 动态内存:如果结构体内部包含指针(char* name),那么简单的 赋值(深拷贝)只会复制指针本身,而不会复制指针指向的数据,这会导致两个结构体的指针指向同一块内存,在释放时需要特别注意,通常需要自己编写“深拷贝”函数来处理这种情况,现代 C++ 通过拷贝构造函数和赋值运算符重载优雅地解决了这个问题,但在 C 语言中需要程序员自己小心处理。
-- 展开阅读全文 --
头像
C语言delay头文件是哪个?如何使用?
« 上一篇 今天
织梦网站图片上传失败怎么办?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]