本文详解C语言的结构体的深浅拷贝、赋值与传参问题,基础永远直接花85%时间去好好学习,小而汇多,建议收藏!
实际上,这并非为结构体而定的,是针对指针变量与其他值变量的特性,这里仅仅用结构体来说明。
一、深浅拷贝
1.浅拷贝
一句话:浅拷贝是针对指针变量的,是指指针变量仅仅指向赋值号右边已经一块已经存在的内存资源,而不是重新开辟一块内存资源,再将值赋值进去。所以两者共享内存资源,相互牵连。
2.深拷贝
一句话:浅拷贝是针对指针变量的,是指指针变量重新开辟一块内存资源,并将里面的值设置为赋值号右边的相对应的内存资源里面的值,所以两者相互独立,互不干扰。
记住,深浅拷贝都是针对指针变量而言的!本质上 “深浅拷贝” 的核心区别在于是否复制指针指向的深层数据。
除了指针变量之外,其余赋值都是深拷贝。
可能纯文本讲解较为枯燥,将在下面的结构体复制中举例子。
二、结构体赋值
1.结构体值间赋值
结构体值变量之间可以直接用 = 赋值,但如果含有指针成员,注意其是浅拷贝。
下文的浅赋值和浅拷贝等同。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct person_
{
char *name;
int age;
};
void print_struct(struct person_ *person)
{
printf("addr=%p,addr_name=%p,addr_age=%p\t\tname:%s\t\tage:%d\n", person, person->name, &person->age, person->name, person->age);
}
int main()
{
/*结构体赋值是结构体成员的赋值,结构体本身的地址还是在原地的,只不过成员如果有指针,那么结构体的指针会指向赋值的结构体的内存地址,但不新开辟空间,其余变量依旧是占用原来结构体的内存*/
/*总的来说,结构体的赋值唯一的看点就是结构体是不是有指针成员,有的话,就发生浅拷贝,结构体本身的地址不变(显然如此,因为这是一块已经存在磁盘上的资源了)*/
/**
* data descp: 值赋值
*/
printf("value assign\n\n");
printf("before value assign\n");
struct person_ person1 = {"A", 1};
struct person_ person2 = {"B", 2};
print_struct(&person1);
print_struct(&person2);
printf("after value assign\n");
person1 = person2;
print_struct(&person1);
print_struct(&person2);
return 0;
}
结果:

value assign
before value assign
addr=000000000061FE10,addr_name=0000000000404055,addr_age=000000000061FE18 name:A age:1
addr=000000000061FE00,addr_name=0000000000404057,addr_age=000000000061FE08 name:B age:2
after value assign
addr=000000000061FE10,addr_name=0000000000404057,addr_age=000000000061FE18 name:B age:2
addr=000000000061FE00,addr_name=0000000000404057,addr_age=000000000061FE08 name:B age:2
可以看到,赋值前后,两个结构体的变量自身的地址不变,一个是000000000061FE10,一个是000000000061FE00,说明确确实实为这两个结构体分配内存了,其次,赋值前,两个结构体成员的name不同,一个为0000000000404055,一个为0000000000404057,但是赋值后,都变成了0000000000404057,说明他们指向的内存相同,赋值并没有新开辟内存,这也证明了这就是浅复制!而除了指针外的其他成员,如结构体本身、age成员,内存地址都不变,说明深浅赋值只针对指针成员!
2.结构体指针间赋值
显然,上面的结构体赋值是值与值之间的,那么指针与指针之间呢?首先我们要明白指针的本质是什么?指针是指向一块内存资源的变量,那么我们指针赋值了后,显然是全部浅赋值!因为本质的结构体资源块全都指向一个内存资源了!赋值后大家都共用一块内存。
代码验证:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct person_
{
char *name;
int age;
};
void print_struct(struct person_ *person)
{
printf("addr=%p,addr_name=%p,addr_age=%p\t\tname:%s\t\tage:%d\n", person, person->name, &person->age, person->name, person->age);
}
int main()
{
/**
* data descp: 指针赋值
*/
struct person_ *person1_pointer = (struct person_ *)malloc(sizeof(struct person_));
struct person_ *person2_pointer = (struct person_ *)malloc(sizeof(struct person_));
person1_pointer->name = (char *)malloc(sizeof(char) * 100);
person2_pointer->name = (char *)malloc(sizeof(char) * 100);
sprintf(person1_pointer->name, "%s", "A");
sprintf(person2_pointer->name, "%s", "B");
person1_pointer->age = 1;
person2_pointer->age = 2;
printf("\n\npointer assign\n");
printf("before pointer assign\n");
print_struct(person1_pointer);
print_struct(person2_pointer);
printf("after pointer assign\n");
person1_pointer = person2_pointer;
print_struct(person1_pointer);
print_struct(person2_pointer);
return 0;
}
结果:

pointer assign
before pointer assign
addr=00000000006F7700,addr_name=00000000006F7740,addr_age=00000000006F7708 name:A age:1
addr=00000000006F7720,addr_name=00000000006F77B0,addr_age=00000000006F7728 name:B age:2
after pointer assign
addr=00000000006F7720,addr_name=00000000006F77B0,addr_age=00000000006F7728 name:B age:2
addr=00000000006F7720,addr_name=00000000006F77B0,addr_age=00000000006F7728 name:B age:2
显然,在赋值前,大家各自指向各自的内存资源,赋值之后,两个所指向的内存资源完全相同了!这是完完全全的浅赋值,验证了我们上面的猜想!
本质上,这还是指针的深入理解!
三、结构体传参
这本质上还是指针传参与值传参的区别!
1.记住一句话:
传参一定发生深拷贝,拷贝的内存区域是栈。只不过拷贝的是值的话,就会造成内存浪费;拷贝的是指针的话,指向相同的内存资源,可以节约内存!
值传参,会将传入函数的参数拷贝一遍,实际上传入的是刚刚拷贝的值(也就是深拷贝),而非函数外的参数。
指针传参,指针变量是深拷贝,但是指针变量指向的值相同,所以指向的就是外面的参数,两者共用内存!
2.代码演示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct person_
{
char *name;
int age;
};
/**
* func descp: 通过值传递结构体,会进行值拷贝,也就是在内存(栈)中会重新开辟一块空间,将传入的参数复制一遍再在里面进行操作,不是本体
*/
void print_struct_by_value(struct person_ person)
{
printf("deep_copy of poniter:%p\n", &person);
printf("addr=%p,addr_name=%p,addr_age=%p\t\tname:%s\t\tage:%d\n", &person, &person.name, &person.age, person.name, person.age);
}
/**
* func descp: 通过指针传递,始终是使用的一个结构体对象,避免无用的复制开销。
*/
void print_struct_by_pointer(struct person_ *person)
{
printf("deep_copy of args:%p\n", &person);
printf("addr=%p,addr_name=%p,addr_age=%p\t\tname:%s\t\tage:%d\n", person, &person->name, &person->age, person->name, person->age);
}
int main()
{
struct person_ person1 = {"Mike", 15};
printf("value as args\n\n");
printf("print object info out args\n");
printf("addr=%p,addr_name=%p,addr_age=%p\t\tname:%s\t\tage:%d\n\n", &person1, &person1.name, &person1.age, person1.name, person1.age);
printf("print object info as value args\n");
print_struct_by_value(person1);
printf("\n\npointter as args\n");
printf("print object info out args\n");
printf("addr=%p,addr_name=%p,addr_age=%p\t\tname:%s\t\tage:%d\n\n", &person1, &person1.name, &person1.age, person1.name, person1.age);
printf("print object info as poniter args\n");
print_struct_by_pointer(&person1);
return 0;
}
结果

value as args
print object info out args
addr=000000000061FE10,addr_name=000000000061FE10,addr_age=000000000061FE18 name:Mike age:15
print object info as value args
deep_copy of poniter:000000000061FDA0
addr=000000000061FDA0,addr_name=000000000061FDA0,addr_age=000000000061FDA8 name:Mike age:15
pointter as args
print object info out args
addr=000000000061FE10,addr_name=000000000061FE10,addr_age=000000000061FE18 name:Mike age:15
print object info as poniter args
deep_copy of args:000000000061FDD0
addr=000000000061FE10,addr_name=000000000061FE10,addr_age=000000000061FE18 name:Mike age:15
通过代码结果可以验证:
| 编号 | 特性 |
|---|---|
| 1 | 传参一定发生深拷贝,在栈上 |
| 2 | 通过值传参,深拷贝值,拷贝开销大 |
| 3 | 通过指针传参,深拷贝指针,拷贝开销小,同时以指针为中介,可以达到函数内部与传入参数共享内存的目的。 |
结尾
好了这就是结构体(指针与值变量)深浅拷贝、赋值与传参的总结,你学会了吗?
基础永远直接花85%时间去好好学习,小而汇多。还是那句话,
别看简单,简单不练,也是难。别看难,难也是简单。

本系列将直击C语言的本质基础,流利处理出各个易错、实用的实战点,并非从零开始学习C。
专注讲解Linux中的常用命令,共计发布100+文章。
本系列将精讲Linux0.11内核中的每一个文件,共计会发布100+文章。
😉【Linux102】11-kernel/vsprintf.c
😉【Linux102】12-include/stdarg.h
和Linux内核102系列不同,本系列将会从全局描绘Linux内核的各个模块,而非逐行源码分析,适合想对Linux系统有宏观了解的家人阅读。
😉【Linux】Linux概述1-linux对物理内存的使用
关于小希
😉嘿嘿嘿,我是小希,专注Linux内核领域,同时讲解C语言、汇编等知识。
我的微信:C_Linux_Cloud,期待与您学习交流!

加微信请备注哦
小希的座右铭:
别看简单,简单也是难。别看难,难也是简单。我的文章都是讲述简单的知识,如果你喜欢这种风格:
下一期想看什么?在评论区留言吧!我们下期见!

1324

被折叠的 条评论
为什么被折叠?



