一文详解C++拷贝构造函数

文章目录

  • 引入
  • 一、什么是拷贝构造函数?
  • 二、什么情况下使用拷贝构造函数?
  • 三、使用拷贝构造函数需要注意什么?
  • 四、深拷贝和浅拷贝
    • 浅拷贝
    • 深拷贝


引入

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。
一文详解C++拷贝构造函数_第1张图片
相当于就是把自己复制一遍,内置类型如int,char这些要实现复制很简单只需要:

int a = 10;
int b = a;

那在创建类对象时,如何创建一个与已存在对象一摸一样的新对象呢?
答案是拷贝构造。

class Date
{
public:
    Date(int year = 2024, int month = 1, int day = 25)//全缺省构造函数
    {
        _year = year;
        _month = month;
        _day = day;
    }
    Date(const Date& d)//拷贝构造
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    ~Date()//析构函数
    {
        //cout << "~Time()" << endl;
    }
    void show()//普通函数
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};


int main()
{
    Date a(2000, 1, 1);
    Date b = a; //注意这里的对象初始化要调用拷贝构造函数,而非赋值
    b.show();
    return 0;
}

一文详解C++拷贝构造函数_第2张图片
从以上代码可以看出系统为对象 B 分配了内存并完成了与对象 A 的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。
具体过程是当编译器执行到Date b = a这一行代码时会自动调用b的拷贝构造函数,拷贝构造的参数为对象a,并在函数中完成赋值操作。


一、什么是拷贝构造函数?

同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制或称拷贝是完全可行的。==这个拷贝过程只需要拷贝数据成员,==而函数成员是共用的(只有一份拷贝)。在建立对象时可用同一类的另一个对象来初始化该对象的存储空间,这时所用的构造函数称为拷贝构造函数。

拷贝构造函数本质上来说也是构造函数,是构造函数的一个重载。

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。


二、什么情况下使用拷贝构造函数?

  • 使用已存在对象创建新对象(用旧对象去初始化新对象)
  • 函数参数类型为类类型对象(从实参传递给形参的过程,是用实参去构造形参)
  • 函数返回值类型为类类型对象(用局部对象去构造临时对象调用拷贝构造)
class Date
{
public:
    Date(int year = 2024, int month = 1, int day = 25)//全缺省构造函数
    {
        cout << "Date(int,int,int):" << this << endl;
    }
    Date(const Date& d)//我们自定义的拷贝构造
    {
        cout << "Date(const Date& d):" << this << endl;
    }
    ~Date()//析构函数
    {
        cout << "~Date():" << this << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
Date Test(Date d)
{
    Date temp(d);
    return temp;
}
int main()
{
    Date d1(2022, 1, 13);
    Test(d1);
    return 0;
}

一文详解C++拷贝构造函数_第3张图片
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
一文详解C++拷贝构造函数_第4张图片


三、使用拷贝构造函数需要注意什么?

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
  • 上面我们知道,实参去构造形参的时候会调用拷贝构造的,如果构造函数没有用引用,那么在调用构造函数进行实参构造形参时就会调用构造函数,调用构造函数就又得用实参构造形参,又有得调用构造函数,这样下去就会导致一直调用构造函数,引发无穷递归调用。
    一文详解C++拷贝构造函数_第5张图片
  1. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

四、深拷贝和浅拷贝

浅拷贝

浅拷贝就是在对象复制时,仅仅只对对象中的数据成员进行简单的赋值,默认的拷贝构造就是浅拷贝,虽然大多数情况下浅拷贝就已经够用了,但如果出现资源申请的情况(申请内存),浅拷贝就会出现一些问题。

以下程序是我们自定义实现了一个栈,其中有个成员变量是指针DataType* _array;,在构造函数中我们在堆区申请了内存,让指针_array指向了内块内存。我们在主函数中写了Stack s2(s1);利用构造函数构造了一个s2,但默认的构造函数时是浅拷贝,在s2的构造函数中相当于有这么一句代码_array=s1._array;。这造成了两个指针指向了同一块内存空间,但这俩指针在不同的对象s1,s2中,在俩对象析构的时候会导致同一块内存空间释放两次,导致错误。

typedef int DataType;
class Stack
{
public:
    Stack(size_t capacity = 10)
    {
        _array = (DataType*)malloc(capacity * sizeof(DataType));
        if (nullptr == _array)
        {
            perror("malloc申请空间失败");
            return;
        }
        _size = 0;
        _capacity = capacity;
    }
    void Push(const DataType& data)
    {
        // CheckCapacity();
        _array[_size] = data;
        _size++;
    }
    ~Stack()
    {
        if (_array)

        {
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
    }
private:
    DataType* _array;
    size_t _size;
    size_t _capacity;
};
int main()
{
    Stack s1;
    s1.Push(1);
    s1.Push(2);
    s1.Push(3);
    s1.Push(4);
    Stack s2(s1);
    return 0;
}

一文详解C++拷贝构造函数_第6张图片


深拷贝

为了解决上述问题 我们就需要给s2中的_array也开辟和s1中的_array一样大小的空间,所以我们就需要深拷贝
在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理:
手动写一个深拷贝的拷贝构造,各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。

    Stack(const Stack& s)
    {
        //s1空间有多大就申请多大的空间
        _array= (DataType*)malloc(s._capacity * sizeof(DataType));
        if (_array == NULL)
        {
            exit(-1);
        }
        for (int i = 0; i < s._size; i++)
        {
            _array[i] = s._array[i];
            _size++;
        }
        _capacity = s._capacity;
    }

你可能感兴趣的:(C++,c++)