笔记十:复制构造函数、深拷贝、浅拷贝

复制构造函数

定义:

只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数成为复制构造函数。复制构造函数可用于:

1、根据另一个同类型的对象显示或隐式初始化一个对象

2、复制一个对象,将它作为实参传递给一个函数

3、从函数返回时复制一个对象

4、初始化顺序容器中的元素

5、根据元素初始化列表初始化数组元素

——以上定义来自《C++ Primer 中文版 第4版》


浅拷贝/浅复制

第一条中,若一个自定义类对象已经初始化了,并且用该类去初始化另一个同类类型的对象,假设类中存在指针型变量,若没有显示的构造复制构造函数,那么编译器会自动生成一个复制构造函数,此时的复制称为浅拷贝。

更准确的定义如下:

在C++中,在用一个对象初始化另一个对象时,只复制了成员,并没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制。

那么浅拷贝存在的风险是什么呢?

若自定义类中存在指针成员函数,那么:

1、浅拷贝只是复制了指针,使得2个指针指向了同一个地址,这样在对象块结束调用函数析构时,会造成对同一资源的2次析构,即delete 2次,引起程序崩溃。

2、浅拷贝使得2个指针成员指向同一个内存地址,修改其中一个指针指向的值,会改变另一个对象的指针指向的值

3、在内存释放时,作为实参传递的类由于析构不成功,造成内存泄露。

以上来自http://blog.csdn.net/feitianxuxue/article/details/9275979

通过一个实例来反应:

代码:

#include<iostream>

using namespace std;

class A
{
public:
    A()
    {
        data = new char;
        cout << data << endl;
        cout << "默认构造函数" << endl;
    }
    ~A()
    {
        cout << "析构函数" << endl;
        delete data;
        cout << data << endl;
        data = NULL;
    }
private:
    char* data;
};

int main(int argc, char* argv[])
{
    A a;
    A b(a);

    return 0;
}

针对以上代码,进行单步,逐语句的调试:

1、在A a; 处设置断点,采取逐语句F11调试,进入到自定义构造函数中:

2、对于对象a——动态分配一个内存给data,此时data的值为0x004084f8,存储data这个值得内存地址为0x002ffbf4 :

3、继续执行F11,会发现程序不会再次进入到自定义构造函数,因为此时A b(a) 执行的复制构造函数,由于类没有显示定义,故由编译器自动完成,程序运行到:

4、在main函数结束之前,由于对象的声明周期已到,故此时需调用析构函数,且析构函数的顺序是由后往前析构,即对象b在对象a之后定义,那么b在a之前析构。通过观察也可以判断析构顺序,此时存储data变量的地址为0x002ffbe8 与a中存储data的地址不同。

但是a, b中data值均为0x004084f8

5、delete释放掉内存资源后,data的值变为0x004084f8<字符串中的字符无法…>:( 个人理解,这里的0x004084f8<字符串中的字符无法…>0x004084f8<妄…>有本质区别,后者内存地址对应着一个实际的内存空间,内存中存储的数据显示乱码。而前者尽管看似为一个内存地址值,但并未对应到一个实际的内存空间,好比一个学号之前是可以对应一个学生的,但是学生信息注销之后,尽管该学号存在,但是无法查找到此人。若理解有误,恳请指正,虚心学习~ ,此时data成为一个垂悬指针。

6、避免垂悬指针的存在,将指针指向NULL:

7、第一次析构结束:

8、第2次析构,即对象a的析构。此时可以观察到data存储的值已经变为0x004084f8<字符串中的字符无法…>,即data是一个野指针了。

9、继续析构,则导致程序崩溃:

上述调试则解释了浅拷贝可能造成的影响即:

1、同一内存空间析构2次引起程序崩溃

2、一个类成员修改资源,会使得另一个也随之改变

3、第3点有疑问,data指向的内存存储在堆中,但是已经由对象b给释放了,而data是一个局部变量,其储存地址在栈中,程序结束,系统自动收回栈中的资源,那么此时所谓的内存泄露是泄露了哪一部分内存资源呢???表示不太理解,或许我理解有误???

深拷贝/深复制

定义:

当拷贝对象中有对其他资源(如堆、文件、系统等)的引用时(引用可以是指针或引用)时,对象另开辟一块新的资源,而不再对拷贝对象中资源的指针或引用进行单纯的赋值。简单地说,即是非共享同一块内存资源,而是重新开辟一块内容,将数据复制到新开辟的内存中。

通过一个实例来反应:

#include<iostream>

using namespace std;

class A
{
public:
    A()
    {
        data = new char;
        cout << data << endl;
        cout << "默认构造函数" << endl;
    }
    A(const A& a)
    {
        data = new char;
        memcpy(data, a.data, sizeof(a.data));
    }
    ~A()
    {
        cout << "析构函数" << endl;
        delete data;
        cout << data << endl;
        data = NULL;
    }
private:
    char* data;
};

int main(int argc, char* argv[])
{
    A a;
    A b(a);

    return 0;
}

采用单步调试F11:

1、程序执行到对象a的实例化:

2、进入对象a的构造函数中:

3、此时在堆中为data开辟了一个内存,内存地址为0x005584f8

4、注意,在浅复制时,程序直接运行到return 0; 。而深复制,F11后进入复制构造函数:

5、观察此时data指向的内存地址为0x00558d10a.data(0x005584f8)是不同的。memcpy的目的即为复制数据。

6、执行对象b的析构,此时data的值为0x00558d10,正好是b对象复制构造中分配的内存空间的地址。

7、此时对象b占据的堆中的资源被释放。

8、第2次析构,即a对象中资源的释放,此时data的值为0x005584f8

9、程序没有出现崩溃的状况,表明2次析构成功。

那么,什么时候用浅拷贝?什么时候用深拷贝呢?

http://www.cricode.com/753.html 中认为最好使用深拷贝或智能指针。深拷贝相对于浅拷贝来说,会占据额外的内存资源,但是使用更加安全。

时间: 2025-02-01 04:19:59

笔记十:复制构造函数、深拷贝、浅拷贝的相关文章

C++自学笔记_复制构造函数_《C++ Primer》

在内置数据类型中,一般可以用一个变量初始化另一个变量.同样,对于类类型的对象,也可以用一个对象初始化另一个对象,编译器会合成一个复制构造函数. #include <iostream> using namespace std; class Point{ public: Point(int x=0,int y=0):xPos(x),yPos(y){} void printPoint(){ cout<<"xPos:"<<xPos<<endl;

C++拷贝构造函数(深拷贝&amp;浅拷贝)

对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=88; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. <iostream> using namespace std; class CExample { private:      int a; public:      CExample(int b)      { a=b;}      void Show ()      {         cout

Python深复制浅复制or深拷贝浅拷贝

1. copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象.(比深拷贝更加节省内存) 2. copy.deepcopy 深拷贝 拷贝对象及其子对象 小例子: import copy a = [1, 2, 3, 4, ['a', 'b', 'c']] b = a c = copy.copy(a) d = copy.deepcopy(a) print(id(a))#2552169009288 print(id(b))#2552169009288 print(id(c))#255216

【转载】C++拷贝构造函数(深拷贝,浅拷贝)

对于普通类型的对象来说,它们之间的复制是很简单的,例如:int a=88;int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. #include <iostream>using namespace std;class CExample {private:     int a;public:     CExample(int b)     { a=b;}     void Show ()     {        cout<

【转】 c++拷贝构造函数(深拷贝,浅拷贝)详解

c++拷贝构造函数(深拷贝,浅拷贝)详解 2013-11-05 20:30:29 分类: C/C++ 原文地址:http://blog.chinaunix.net/uid-28977986-id-3977861.html 一.什么是拷贝构造函数      首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=100; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.  下面看一个类对象拷贝的简单例子. #include<iostream

拷贝构造函数(深拷贝vs浅拷贝)

类对象之间的初始化是由类的拷贝构造函数完成的.它是一种特殊的构造函数,它的作用是用一个已知的对象来初始化另一个对象.如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝.位拷贝又称浅拷贝. 一.拷贝构造函数定义格式 类名::拷贝构造函数名(类名& 引用名) 例如: Tdate ::Tdate(Tdate & d); //形参是一个对象的引用 CString( const CString & stringSrc );

C++的简单总结(复制构造函数,深拷贝,前拷贝,默认属性)

类的三大属性: private,public,protected 1,对于类的成员变量或者函数,缺省即为私有 #include <iostream> using namespace std; class A { int y; //私有成员 int x; //私有成员 public: A(int xx, int yy) {x = xx; y = yy;} void setx(int m) {x = m;} void sety(int n) {y = n;} }; int main() { A a

C++ Primer 学习笔记_54_类与数据抽象 --复制构造函数、赋值操作符

复制控制 --复制构造函数.赋值操作符 引言: 当定义一个新类型时,需要显式或隐式地指定复制.赋值和撤销该类型的对象时会发生什么– 复制构造函数.赋值操作符和析构函数的作用!      复制构造函数:具有单个形参,该形参(常用const修饰)是对该类类型的引用.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式的使用复制构造函数:当将该类型的对象传递给函数或者从函数返回该类型的对象时,将隐式使用复制构造函数.     析构函数:作为构造函数的互补,当对象超出作用域或动态分配的对象被删除

Python中list的复制及深拷贝与浅拷贝探究

在Python中,经常要对一个list进行复制.对于复制,自然的就有深拷贝与浅拷贝问题.深拷贝与浅拷贝的区别在于,当从原本的list复制出新的list之后,修改其中的任意一个是否会对另一个造成影响,即这两个list在内存中是否储存在同一个区域,这也是区分深拷贝与浅拷贝的重要依据.接下来我们就针对Python中list复制的几种方法,来探究一下其是属于深拷贝还是浅拷贝.弄清楚这个问题,有助于我们在编程中规避错误,减少不必要的调试时间. 一.非拷贝方法--直接赋值 如果用=直接赋值,是非拷贝方法.这