C++深复制和浅复制

对象的复制

   对于普通类型的对象来说,它们之间的复制是很简单的,例如:

int a=88;
int b=a;
double f=3.12;
double d(f);

  而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种数据成员。下面看一个类对象复制的简单例子。

#include <iostream>
using namespace std;
class Test
{
private:
    int a,b;
public:
    Test(int x, int y)     //提供的形式参数,是为了给数据成员直接初始化的
    {
        a=x;
        b=y;
    }
    Test(const Test& C)   //复制构造函数,提供一个同类型对象作为参数
    {
        a=C.a;
        b=C.b;
    }
    void show ()
    {
        cout<<a<<"  "<<b<<endl;
    }
};

int main()
{
    Test a(100,10);    //执行构造函数Test::Test(int x, int y)
    Test b(a);      //执行构造函数Test::Test(const Test& C)
    Test c=a;      //也执行构造函数Test::Test(const Test& C)
    b.show();
    c.show();
    return 0;
}

  运行程序,屏幕输出两行100   10。

  从以上代码的运行结果可以看出,系统在声明对象b和c时,完成了由对象a的复制。

 复制构造函数

  就类对象而言,相同类型的类对象是通过复制构造函数来完成整个复制过程的。

  上例中的Test::Test(const Test& C),就是我们自定义的复制构造函数

  可见,复制构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,用来约束作为参数的对象在构造新对象中是不能被改变的。

  略一归纳:类X的复制构造函数的形式为X(X& x)。

  当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,复制构造函数就会被自动调用。也就是说,当类的对象需要复制时,复制构造函数将会被调用。以下情况都会调用复制构造函数:

  • 一个对象以值传递的方式传入函数体
  • 一个对象以值传递的方式从函数返回
  • 一个对象需要通过另外一个对象进行初始化。

  如果在类中没有显式地声明一个复制构造函数,那么,编译器将会自动生成一个默认的复制构造函数,该构造函数完成对象之间的浅复制,后面将进行说明。

  自定义复制构造函数是一种良好的编程风格,它可以阻止编译器形成默认的复制构造函数,提高源码效率。

 浅复制和深复制

  所谓浅复制,如同上面出现过的构造函数中处理的一样,直接为数据成员赋值即可。在很多情况下,这是可以的。创建新的对象,要为对象的数据成员分配存储空间,直接赋值就将值保存在相应的空间中。

  然而,这种浅复制,却并不能通行天下,下面的程序中,浅复制带来了问题。

#include <iostream>
#include <cstring>
using namespace std;
class Test
{
private:
    int a;
    char *str;
public:
    Test(int b, char *s)
    {
        a=b;
        strcpy(str,s);  //肇事地点,但不是祸端
    }
    Test(const Test& C)
    {
        a=C.a;
        strcpy(str,C.str);
    }
    void show ()
    {
        cout<<a<<","<<str<<endl;
    }
};

int main()
{
    Test a(100,"hello");
    Test b(a);
    a.show();
    b.show();
    return 0;
}

  程序运行中,会弹出一个窗口:程序的执行意外停止了。面对这个窗口,我们应该有感觉,这和使用指针不当有关系。

  我们从main函数看起。

  当程序执行到第28行Test a(100,"hello");时,对象a的数据成员a获得实际参数100的值,而数据成员str,即指针,是个随机地址值(指针的值,非指针指向的值)!在程序中,试图通过strcpy(str,s);将形式参数s指向的字符串"hello",复制到str所指向的那个位置,而那个位置,其地址并不是经过系统分配来的,这是个危险的操作。在这里,str这样未经过分配的地址(指针),被称为“野指针”。

  在执行第29行Test b(a);时,同样的事情还要发生。

  str这样的野指针是多么的霸道!在有的系统里,这样的行为是被接受的,可以想到其有多危险。有些系统中,不允许这样的事情发生的。于是,上面的程序在codeblock中调试会出错。这个错来的好。

  解决这样的问题的方法,就是在构造函数中,要为指针类型的成员,分配专门的空间。以这条规则构建的复制,称作为深复制!

  上面的程序,改写为:

#include <iostream>
#include <cstring>
using namespace std;
class Test
{
private:
    int a;
    char *str;
public:
    Test(int b, char *s)
    {
        a=b;
        str=new char[strlen(s)+1];  //分配str指向的空间,其长度根据s指向的字符串定。为何加1?字符串结束要用\0
        strcpy(str,s);  //前一程序的肇事地点,祸端已经由上一句摘除
    }
    Test(const Test& C)
    {
        a=C.a;
        str=new char[strlen(C.str)+1];   //同上,这样str就不是野指针了
        strcpy(str,C.str);
    }
    ~Test()
    {
        delete []str;
    }
    void show ()
    {
        cout<<a<<","<<str<<endl;
    }
};

int main()
{
    Test a(100,"hello");
    Test b(a);
    a.show();
    b.show();
    return 0;
}

  好了,a和b对象的str成员,明确地给分配了空间,他们再不是野指针了。因为明确地分配了空间,析构函数中要释放对应的空间。我们不能用野指针,当然,也不能对象要撤销了,还占着空间不放,做事不能这样不厚道。

  深复制就体现在第13和第19行分配指针指向的空间,这段空间的地址,也将是指针的值(分清指针的值和指针指向的值)。

再一个深复制的例子

  下面再给一个例子,类A的数据成员可以保存len个整型数据。类中的数据成员arrayAddr是指向整型的指针,可以作为一个一元数组的起始地址。这个类有指针数据成员,构造函数的定义中,必须要采用深复制的方法,第16行体现了这一点。另外,析构函数中完成了对分配的空间的释放

#include<iostream>
using namespace std;
class A
{
private:
    int *arrayAddr;//保存一个有len个整型元素的数组的首地址
    int len;       //记录动态数组的长度
public:
    A(int *a, int n);
    ~A();
    int sum();
};
A::A(int *a, int n)
{
    len=n;
    arrayAddr=new int[n];  //为指针数据成员分配空间,注意,没有上面例子中加1那回事
    for(int i=0; i<n; i++)  //逐个地将a指向的值逐个地复制过来
    {
        arrayAddr[i]=a[i];
    }
}
//析构函数的类外定义,释放指针型数据a所指向的空间
A::~A()
{
    delete [] arrayAddr;
}
int A::sum()   //获得a指向的数组中下标为i的元素的值
{
    int s=0;
    for(int i=0; i<len; i++)  //逐个地将a指向的值逐个地复制过来
    {
        s+=arrayAddr[i];
    }
    return s;
}

int main(){
    int b[10]= {75, 99, 90, 93, 38, 15, 5, 7, 52, 4};
    A r1(b,10);
    cout<<"和:"<<r1.sum()<<endl;
    int c[15] = {18,68,10,52,3,19,12,100,56,96,95,97,1,4,93};
    A r2(c,15);
    cout<<"和:"<<r2.sum()<<endl;
    return 0;
}

  

==================== 迂者 贺利坚 CSDN博客专栏=================
|== IT学子成长指导专栏 专栏文章的分类目录(不定期更新) ==|
|== C++ 课堂在线专栏  贺利坚课程教学链接(分课程年级) ==|
|== 我写的书——《逆袭大学——传给IT学子的正能量》    ==|
===== 为IT菜鸟起飞铺跑道,和学生一起享受快乐和激情的大学 =====
时间: 2024-12-25 04:48:48

C++深复制和浅复制的相关文章

Java中对象的深复制和浅复制详解

1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象. ⑵深复制(深克隆) 被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量.那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象.换言之,深复制把要复制的对象所引用的对象都复制了一遍. 2.Java的clone()方法 ⑴clone方法将对象复制了一份并返回

理解java中的深复制和浅复制_java

 Java语言的一个优点就是取消了指针的概念,但也导致了许多程序员在编程中常常忽略了对象与引用的区别,本文会试图澄清这一概念.并且由于Java不能通过简单的赋值来解决对象复制的问题,在开发过程中,也常常要要应用clone()方法来复制对象.本文会让你了解什么是影子clone与深度clone,认识它们的区别.优点及缺点.       看到这个标题,是不是有点困惑:Java语言明确说明取消了指针,因为指针往往是在带来方便的同时也是导致代码不安全的根源,同时也会使程序的变得非常复杂难以理解,滥用指针写

js中的深复制和浅复制

在实际情况中经常会遇到对对象复制的问题.比如在处理项目中的一笔多结构的数据存储或者调用,这个时候你就要对对象(json)进行操作,而不 同的操作根据不同的需求来定义.其中最常见最普遍的是对对象的复制,重新定义,扩展等.下面我们正对这些问题来进行探讨.要了解对象,我们首先需要了解 js的内存分配机制: var o = {a:1};当我们在给一个变量赋值的时候已经在浏览器中开辟了一块内存出来.这块内存在浏览器中占了一定的空间,这个时候,我们可以称变量 o 为栈,称{a:1}为堆,他们之间的关系可以用

C#中的深复制和浅复制

C# 支持两种类型:"值类型"和"引用类型". 值类型(Value Type)(如 char.int 和 float).枚举类型和结构类型. 引用类型(Reference Type) 包括类 (Class) 类型.接口类型.委托类型和数组类型. 如何来划分它们? 以它们在计算机内存中如何分配来划分 值类型与引用类型的区别? 1,值类型的变量直接包含其数据, 2,引用类型的变量则存储对象引用. 对于引用类型,两个变量可能引用同一个对象,因此对一个变量的操作可能影响另一

iOS开发之深复制和浅复制

1.概述 对象拷贝有两种方式:浅复制和深复制.顾名思义,浅复制,并不拷贝对象本身,仅仅是拷贝指向对象的指针:深复制是直接拷贝整个对象内存到另一块内存中. 如下图: 再简单些说:浅复制就是指针拷贝:深复制就是内容拷贝.   2.集合的浅复制 (shallow copy) 集合的浅复制有非常多种方法.当你进行浅复制时,会向原始的集合发送retain消息,引用计数加1,同时指针被拷贝到新的集合. 现在让我们看三个浅复制的例子:   ? 1 2 3 4 NSArray *shallowCopyArray

深度解析javascript中的浅复制和深复制

     在谈javascript的浅复制和深复制之前,我们有必要在来讨论下js的数据类型.我们都知道有 Number,Boolean,String,Null,Undefined,Object五种类型.而Object又包含Function,Array 和Object自身.前面的五种类型叫做基本类型,而Object是引用类型.可能有人就要问,为什么要分基本类型和引用类型呢?后面你就会明白的.      我们首先来看看浅复制和深复制的简洁定义: 深复制:直接将数据复制给对应的变量 浅复制:将数据的地

Java中对象的深复制(深克隆)和浅复制(浅克隆)介绍_java

1.浅复制与深复制概念 ⑴浅复制(浅克隆)      被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象. ⑵深复制(深克隆)      被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量.那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象.换言之,深复制把要复制的对象所引用的对象都复制了一遍. 2.Java的clone()方法 ⑴clone方法将

android 浅复制和深复制-clone篇

有关java中的 浅复制和深复制 ,这里不详细讨论.相关知识请参考以下帖子: 浅复制和深复制http://blog.csdn.net/yang_hui1986527/article/details/7029777 浅析Java中的深拷贝与浅拷贝http://blog.csdn.net/yang_hui1986527/article/details/7012428 android中的 浅复制和深复制,具体可能于java中的有一点不同.下面,我们来探讨下在这个问题. 主要思路: 1.创建TextBo

举例区分Python中的浅复制与深复制

  这篇文章主要介绍了举例区分Python中的浅复制与深复制,是Python入门学习中的重要知识,需要的朋友可以参考下 copy模块用于对象的拷贝操作.该模块非常简单,只提供了两个主要的方法: copy.copy 与 copy.deepcopy ,分别表示浅复制与深复制.什么是浅复制,什么是深复制,网上有一卡车一卡车的资料,这里不作详细介绍.复制操作只对复合对象有效.用简单的例子来分别介绍这两个方法. 浅复制只复制对象本身,没有复制该对象所引用的对象. ? 1 2 3 4 5 6 7 8 9 1