C++和Java的浅层拷贝与深层拷贝

1 篇文章 0 订阅

今天上课学到了C++的浅层拷贝与深层拷贝, 于是在这里将C++和Java关于浅层拷贝与深层拷贝做一个对比.

一.C++的浅层拷贝与深层拷贝

  • 先来了解一下C++中的复制构造函数:
//假设有这样一个TEST类:
class TEST
{
    private:
        int *num;
    public:
        TEST(int n)
        {
            num = new int;
            *num = n;
        }

        void change(int anothernum)
        {
            *num = anothernum;
        }

        void print()
        {
            cout << *num << endl;
        }
};

在上述TEST类中并没有显式定义TEST类的复制构造函数, 那么其默认的复制构造函数应为:

TEST(const TEST & t)
{
    num = t.num;
}

1.C++的浅层拷贝

以上述TEST类为例, 假设有如下代码:

#include<iostream>
using namespace std;

class TEST
{
    private:
        int *num;
    public:
        TEST(int n)
        {
            num = new int;
            *num = n;
        }

        void change(int anothernum)
        {
            *num = anothernum;
        }

        void print()
        {
            cout << *num << endl;
        }
};

int main(void)
{
    TEST a(10);
    TEST b(a);

    a.print();
    b.print();

    b.change(100);

    a.print();
    b.print();
}

上述代码中并没有显式定义复制构造函数, 运行结果如下:

10
10
100
100
//即更改了b对象的值, a对象的值也随之改变

上述结果可以证明, C++的默认构造函数为浅层复制, 也就是说a对象和b对象指向同一段内存空间.

在小伙伴的提示下, 上述代码存在一些问题:在TEST类中并没有定义析构函数.析构函数的定义应该如下:

~TEST()
{
        delete[] num;
}

那么定义析构函数和不定义析构函数都会引发什么样的问题呢?

  • 定义析构函数:
    首先a对象执行析构函数, 释放掉a对象所指向的空间之后, b对象再执行析构函数, 会释放掉b对象所指向的空间. 前面提过了, 使用默认的拷贝构造函数拷贝的b对象和a对象应该是指向同样一段内存空间的. 这样等于对一段空间delete了两次, 会造成内存非法访问, 是极为不妥当的!!!
  • 不定义析构函数:
    如果没有构造析构函数, 那么a和b所指向的空间无法得到释放, 这会造成内存泄露, 这样也是极为不妥当的!!!

综上所述, 在TEST类中, 不管是否定义析构函数都是不妥当的, 那这该怎么办呢?

  • 拷贝构造函数中涉及到需要动态分配内存的情况下, 应该自定义拷贝构造函数.
  • 换句话来说, 如果需要自定义析构函数, 那么也应该自定义拷贝构造函数.

2.C++的深层拷贝

依旧来看一段代码:

#include<iostream>
using namespace std;

class TEST
{
    private:
        int *num;
    public:
        TEST(int n)
        {
            num = new int;
            *num = n;
        }
        //此处显示定义了TEST类的复制构造函数
        TEST(const TEST & t)
        {
            num = new int;
            *num = *t.num;
        }

        void change(int anothernum)
        {
            *num = anothernum;
        }

        void print()
        {
            cout << *num << endl;
        }
};

int main(void)
{
    TEST a(10);
    TEST b(a);

    a.print();
    b.print();

    b.change(100);

    a.print();
    b.print();
}

上述代码中显式定义了复制构造函数, 自定义的复制构造函数中, 对a对象在堆上动态申请了空间, 然后再将b对象的值赋值给a对象新申请的这段内存空间.
运行结果如下:

10
10
10
100
//更改b对象的值并没有改变a对象的值, 充分说明a对象和b对象所占的是不同的两段内存空间

二.Java的浅层拷贝和深层拷贝

1.以数组为例, 关于Java浅层拷贝和深层拷贝的几个小知识点

  • 数组复制的两个方法:
    System.arraycopy(array1, 0, array2, array1.length);五个参数分别为:源数组, 源数组起始索引, 目的数组, 源数组长度.
    Arrays.copyOf(array1, array1.length);参数分别为:源数组, 源数组的长度.
  • 在Java中, 只要看到new, 就是建立对象, 即申请一段新的空间. 也就是说, 只要new了, 二者就不是同一个对象,这一点千万要注意!!!
  • 关于Integer类的一点知识:若使用Integer a = xxx;这样的形式打包一个值, 要打包的值在Integercache.low~Integercache.high(-128~127)之间, 若在缓存中没有打包过, 则返回一个new的新对象; 若打包过, 则直接返回打包过的对象; 若不在此范围内, 则直接返回一个new的新对象. 而使用Integer a = new Integer(xxx);这样打包出来的值, 一定是一个新的对象.
  • 关于字符串池:Java为了效率考虑, 凡是以”“写下的字符串都为字符串常量, 以”“包括的字符串, 只要内容相同, 无论在代码中出现多少次, JVM都只会建立一个String对象. 也就是说, “”写下的字符串都会被放入字符串池中, 往后再出现与”“写下字符串相同的字符串, 都将直接参考(意大致等同于C中的指向)至字符串池中已有对象, 不再建立新的对象.

2.Java的深层拷贝

为什么要先谈Java的深层拷贝呢?
因为在Java中, 只要是基本数据类型的数组, 使用上面介绍到的两个数组拷贝函数进行数组拷贝, 都是深层拷贝!
来看如下代码:

public class ArrayCopy {
    public static void main(String args[]){
        int[] array1 = {0, 1, 2, 3, 4, 5};
        int[] array2 = Arrays.copyOf(array1, array1.length);

        for(int num : array1){
            System.out.printf("%3d", num);
        }
        System.out.println();

        for(int num : array2) {
            System.out.printf("%3d", num);
        }
        System.out.println();

        array2[0] = 10;

        for(int num : array1){
            System.out.printf("%3d", num);
        }
        System.out.println();

        for(int num : array2){
            System.out.printf("%3d", num);
        }
    }
}

因为基本类型的数组的拷贝皆为深层拷贝额, 所以更改array2数组第一个元素的值, 并不会影响array1数组第一个元素的值. 运行结果如下:

  0  1  2  3  4  5
  0  1  2  3  4  5
  0  1  2  3  4  5
 10  1  2  3  4  5

3.Java的浅层拷贝

无论是使用System.arraycopy()还是Arrays.copyOf(), 只要用作类类型声明的数组时, 都执行浅层拷贝, 即源数组与拷贝数组指向同一段内存空间.
需要特别说明的是, 数组在Java中也是类的对象, 所以二维数组和三维数组在使用System.arraycopy()和Arrays.copyOf()的时候, 执行的也是浅层拷贝.
关于浅层拷贝就不在这里举例子了, 下面来看一看, 如何让类类型的数组执行深层拷贝.

4.使类类型的数组执行深层拷贝

看如下代码:

public class DeepCopy {
    public static class cloths{
        String color;
        char size;

        cloths(String col, char si){
            color = col;
            size = si;
        }
    }

    public static void main(String[] args){
        cloths[] c1 = {new cloths("red", 'l'), new cloths("blue", 'm')};
        cloths[] c2 = new cloths[c1.length];

        for(int i = 0; i < c1.length; i++){
            cloths c = new cloths(c1[i].color, c1[i].size);
            c2[i] = c;
        }

        c1[0].color = "yellow";
        System.out.println(c2[0].color);
    }
}

上述代码, 在复制每一个类类型的数组元素时, 都给其new一段新的空间, 使之与源数组元素完全隔离开. 所以运行结果如下:

red
//源数组的第一个元素的color并没有被改变
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值