查看原文
其他

结构体成员赋值到底是深拷贝还是浅拷贝?

守望先生 编程珠玑 2022-09-10

来源:公众号【编程珠玑】

作者:守望先生

ID:shouwangxiansheng

在《C语言容易忽略的知识点》一文中,有读者说这种结构体复杂成员赋值的的拷贝是浅拷贝(感谢读者提出),那么到底什么是深拷贝,什么是浅拷贝?

浅拷贝

浅拷贝指的是仅拷贝对象的所有成员,而不包括其引用对象(例如指针指向的其他内容)。我们来看C和C++的例子。

C++的例子如下:

//来源:公众号【编程珠玑】
//https://www.yanbinghu.com
#include <iostream>
class Test
{

public:
    /*构造函数*/
    Test():a(0)
    {
        std::cout<<"new b"<<std::endl;
        b = new char[16];
    }
    /*析构函数*/
    ~Test()
    {
        std::cout<<"delete b"<<std::endl;
        delete [] b;
    }
private:
    int a;
    char *b;
};
int main()
{
    Test test;
    Test test0 = test;//浅拷贝
    return 0;
}

运行结果:

new b                                                          
delete b                                                         
delete b
core dumped

可以看到,在拷贝test的时候,只拷贝了其成员本身的值,即a和b的值,而b只是一个指针,它指向的内容却没有被拷贝,因此我们说,它是浅拷贝。而对象test在被销毁时,会释放b指向的内存,test0被销毁时,又进行了重复释放,因此导致core dumped。

其拷贝过程如下图所示:

浅拷贝

C语言例子:

//来源:公众号编程珠玑
//https://www.yanbinghu.com
#include<stdio.h>
#include<stdlib.h>
typedef struct Member_t
{

    char *p;
    int c;
}Member;
typedef struct Test_t
{

    int a;
    Member b;
}Test;
int main(void)
{
    Test test0;
    test0.a = 10;
    test0.b.p = malloc(16);
    if(NULL == test0.b.p)
    {
        printf("malloc failed\n");
    }
    snprintf(test0.b.p,16,"hello");
    test0.b.c = 24;

    /*拷贝*/
    Test test1;
    test1.a = test0.a;
    test1.b = test0.b;

    /*修改*/
    snprintf(test1.b.p,16,"world");

    printf("%s\n",test0.b.p);
    free(test0.b.p);
    test0.b.p = NULL;
    return 0;

}

运行结果:

world

由于其结构体成员赋值时,只拷贝其成员本身的值,即

test1.b = test0.b

只拷贝了其中的p的值和c的值,却没有拷贝p指向的内存,因此拷贝之后,两者的p指向同一片内存区域,导致通过其中一个修改就会影响另外一个的内容。因此它也是浅拷贝。(感谢在上篇中读者指出)

深拷贝

深拷贝除了拷贝其成员本身的值之外,还拷贝的成员指向的动态内存区域等类似的内容。
那么对于前面的例子,我们如何进行深拷贝呢?以C++为例,我们需要定义自己的拷贝构造函数:

Test(Test &t)
{
    std::cout<<"copy"<<std::endl;
    a = t.a;
    b = new char[16];
    /**拷贝b指向的内容**/
    memcpy(b,t.b,16);
}

这里就不是拷贝指针b的值,而是拷贝指针b指向的内容。因此是深拷贝。再次运行结果:

new b     
copy
delete b                                                         
delete b

这种情况下,test和test0中b的值是不一样的,但是b指向的内容是一样的。

那么C语言中怎么处理呢?自然就是需要拷贝成员b中p指向的内容了。这里就留给读者自己去实现了。

深拷贝过程如下:

深拷贝

C语言里的深拷贝与浅拷贝

作为使用C语言的读者来说,我觉得到没有必要去抓什么深拷贝与浅拷贝的概念,你只需要理解,C里面的赋值类的拷贝,仅仅是拷贝值而已,比如你拷贝的是指针,那么只是拷贝指针的值,指针指向的区域是不会拷贝的;而如果你拷贝的是数组,那么将会拷贝数组的值,而不是数组首地址(参考《C语言容易忽略的知识点》中的例子)。

结构体赋值

那么回到结构体赋值成员赋值的问题。根据上面的分析可以知道,如果结构体成员都是基本数据类型或者数组(非指针),那么直接赋值是没有任何问题的,而且非常地方便,而如果成员有指针类型,你又不想复制的结构体成员指向相同的内存区域,那么你就需要自己拷贝其指向的内容。

关于数组和指针,请参考《数组之谜》。

总结

默认的拷贝行为基本都是浅拷贝,即仅仅拷贝其成员值。当然如果所有成员值没有引用任何外部对象,或者引用的外部对象定义了自己的深拷贝行为,那么深拷贝和浅拷贝是一样的。如果需要拷贝值以外的内容,请自己定义拷贝行为。

最后,一张图理解深拷贝和浅拷贝:

深拷贝和浅拷

最后关于C语言,自动动手,丰衣足食。

另外,有些概念是为了更好说明某个点,如果这个概念不能帮助你理解这个点,那么请关注这个点本身。

最后推荐C入门书:



关注公众号【编程珠玑】,获取更多Linux/C/C++/数据结构与算法/计算机基础/工具等原创技术文章。后台免费获取经典电子书和视频资源


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存