【C语言】深浅拷贝、传参、赋值 本质剖析


公粽号「专注Linux」,专注Linux内核开发

本文详解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%时间去好好学习,小而汇多。还是那句话,别看简单,简单不练,也是难。别看难,难也是简单。



汇编语言

😉【汇编语言】1—基础硬件知识

😉【汇编语言】2—寄存器基础知识

😉【汇编语言】3-寄存器与内存的交互

😉【汇编语言】4-第一个完整的汇编程序

😉【汇编语言】5-[BX]和loop指令

😉【汇编语言】6-包含多个段的程序

😉【汇编语言】7-灵活的5大寻址方式

😉【汇编语言】8-1-数据的位置

😉【汇编语言】8-2-数据的长度

😉【汇编语言】8-数据处理的两个基本问题(位置与长度)

😉【DOSBox】1-debug

😉【DOSBox】2-debug可执行文件

😉【DOSBox】3-完整开发流程


C语言

本系列将直击C语言的本质基础,流利处理出各个易错、实用的实战点,并非从零开始学习C。

😉【C语言】C Token(C89 C99 C11)

😉【C语言】指针基础

😉【C语言】数组基础

😉【C语言】结构体对齐

😉【C语言】华为C语言进阶测试

😉【C语言】触发刷新到磁盘的方式总结

😉【C语言】C语言文件操作的mode详解

😉【C语言】C语言文件知识全讲解

😉【C语言】从extern到头文件包含的最优实践

😉【C语言】C语言的声明分析

😉【C语言】C语言的关键字与重载机制

😉【C语言】长字符串的2种处理方式

😉【C语言】C语言嵌入汇编程序

😉【C语言】find-in-linux递归搜索文件名函数

😉【C语言】find-in-linux递归搜索文件名函数

😉【C语言】指针数组 VS 数组指针 原来这么简单!

😉【C陷阱与缺陷】-1-词法陷阱

😉【C陷阱与缺陷】-2-语法陷阱

😉【C陷阱与缺陷】-3-语义陷阱


Linux101系列

专注讲解Linux中的常用命令,共计发布100+文章。

😉【Linux101-1】ls

😉【Linux101-1】ls -l命令输出结果全解析

😉【Linux101-2】cd

😉【Linux101-3】cat

😉【Linux101-4】tac

😉【Linux101-5】head

😉【Linux101-6】tail

😉【Linux101-7】pwd

😉【Linux101-8】touch

😉【Linux101-9】cal

😉【Linux101-10】bc

😉【Linux101-11】df

😉【Linux101-12】uname

😉【Linux101-13】mkdir

😉【Linux101-14】gzip

😉【Linux101-15】tar

😉【Linux101-16】lsof

😉【Linux101-17】du

😉【Linux101-18】stat


Linux102系列

本系列将精讲Linux0.11内核中的每一个文件,共计会发布100+文章。

😉【Linux102】1-Makefile

😉【Linux102】2-Makefile.header

😉【Linux102】3-system.map

😉【Linux102】4-bootsect.s

😉【Linux102】5-setup.s

😉【Linux102】6-head.s

😉【Linux102-D】/boot

😉【Linux102】7-main.c

😉【Linux102】8-kernel/asm.s

😉【Linux102】9-kernel/traps.c

😉【Linux102】10-kernel/printk.c

😉【Linux102】11-kernel/vsprintf.c

😉【Linux102】12-include/stdarg.h

😉【Linux102】13-kernel/mktime.c


Linux内核精讲系列

和Linux内核102系列不同,本系列将会从全局描绘Linux内核的各个模块,而非逐行源码分析,适合想对Linux系统有宏观了解的家人阅读。

😉【Linux】学习Linux前必备的知识点

😉【Linux】Linux内核对进程的内存抽象

😉【Linux】Linux概述1-linux对物理内存的使用

😉【Linux】软件从写下到运行的全部流程

😉【Linux】CPU的三寻址:实模式、保护模式、长模式

😉【Linux】实模式与保护模式的寻址, 这次讲明白了!

😉【Linux】linux0.11的源码目录架构

😉【Linux】Makefile机制及基础详解

😉【Linux】编译并运行Linux0.11

😉【Linux】“进进内网文”—Linux的内核结构全貌

😉【Linux】linux的中断机制

😉【Linux】linux进程描述



关于小希

😉嘿嘿嘿,我是小希,专注Linux内核领域,同时讲解C语言汇编等知识。

我的微信:C_Linux_Cloud,期待与您学习交流!

加微信请备注哦


小希的座右铭:别看简单,简单也是难。别看难,难也是简单。我的文章都是讲述简单的知识,如果你喜欢这种风格:

不妨关注、评论、转发,让更多朋友看到哦~~~🙈

下一期想看什么?在评论区留言吧!我们下期见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值