Bootstrap

C++类中的默认成员函数之一:拷贝构造函数

目录

1.拷贝构造函数的定义及特征

2.三种经典调用场景 以值传递的方式从函数返回  作为参数被传入函数    赋值给另一个对象

3.默认的拷贝构造函数(浅拷贝)


1.拷贝构造函数的定义及特征

        1.拷贝构造函数的定义:自定义类型 用同类对象初始化创建另一个对象的时候就会调用
        2.拷贝构造函数的特征:本质是默认构造函数的一个重载形式 只能有一个参数 还必须是引用!为什么必须是引用?
        因为传值会引发无穷递归(因为只要对自定义类型进行赋值操作 就会调用拷贝构造函数 不用引用也起不到避免开辟新空间 提升效率的目的)

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    // Date(const Date& d)   // 正确写法
    Date(const Date d)   // 错误写法:编译报错,会引发无穷递归 
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    Date d2(d1);
    return 0;
}

 报错信息如下:

引发的无穷递归:

那为什么要加上const?因为避免出现意想不到的错误(比如逆序 this变成右边的了)防止不小心修改传进来的引用对象的值 如下图:


2.三种经典调用场景 以值传递的方式从函数返回  作为参数被传入函数    赋值给另一个对象

(此部分的代码和解释部分截取前辈的内容)

C++拷贝构造函数详解_默认拷贝构造函数是深拷贝吗-CSDN博客

1.作为参数被传入函数

class CExample 
{
private:
 int a;

public:
 //构造函数
 CExample(int b)
 { 
  a = b;
  cout<<"creat: "<<a<<endl;
 }

 //拷贝构造
 CExample(const CExample& C)
 {
  a = C.a;
  cout<<"copy"<<endl;
 }
 
 //析构函数
 ~CExample()
 {
  cout<< "delete: "<<a<<endl;
 }

     void Show ()
 {
         cout<<a<<endl;
     }
};

//全局函数,传入的是对象
void g_Fun(CExample C)
{
 cout<<"test"<<endl;
}

int main()
{
 CExample test(1);
 //传入对象
 g_Fun(test);

 return 0;
}

调用g_Fun()时,会产生以下几个重要步骤:
(1).test对象传入形参时,会先会产生一个临时变量,就叫 C 吧。
(2).然后调用拷贝构造函数把test的值给C。 整个这两个步骤有点像:CExample C(test);
(3).等g_Fun()执行完后, 析构掉 C 对象。

2.以值传递的方式从函数返回

class CExample 
{
private:
 int a;

public:
 //构造函数
 CExample(int b)
 { 
  a = b;
 }

 //拷贝构造
 CExample(const CExample& C)
 {
  a = C.a;
  cout<<"copy"<<endl;
 }

     void Show ()
     {
         cout<<a<<endl;
     }
};

//全局函数
CExample g_Fun()
{
 CExample temp(0);
 return temp;
}

int main()
{
 g_Fun();
 return 0;
}

当g_Fun()函数执行到return时,会产生以下几个重要步骤:
(1).产生一个临时变量,就叫XXXX吧。
(2). 然后调用拷贝构造函数把temp的值给XXXX。整个这两个步骤有点像:CExample XXXX(temp);
(3). 在函数执行到最后先析构temp局部变量。
(4). 等g_Fun()执行完后再析构掉XXXX对象

3.给另一个对象赋值

CExample A(100);
CExample B = A; 
// CExample B(A);

后两句都会调用拷贝构造函数

综合以上三种使用场景:

class Date
{
public:
    Date(int year, int minute, int day)
    {
        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;
}

输出如下:

图解如下:


3.默认的拷贝构造函数(浅拷贝)

如果没有显式定义拷贝构造函数,编译器会默认定义一个,对内置类型进行浅拷贝的默认的拷贝构造函数,对自定义类型的成员变量调用它的拷贝构造函数。
默认的拷贝构造函数对象按内存存储按 字节序  完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

​
class Time
{
public:
    Time()
    {
        _hour = 1;
        _minute = 1;
        _second = 1;
    }
    Time(const Time& t)
    {
        _hour = t._hour;
        _minute = t._minute;
        _second = t._second;
        cout << "Time::Time(const Time&)" << endl;
    }
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型 
    Time _t;
};
int main()
{
    Date d1;
    // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
    // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数 
    Date d2(d1);
    return 0;
}

​

成功运行:

但是浅拷贝也会有问题:

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;
}

因为浅拷贝是按照字节序 直接把内容赋值给新的对象,所以两个s1,s2都会指向同一块动态内存空间,当析构时,s2先于s1已经释放了_array的空间,s1再去释放一遍,会导致报错。

所以这个时候就需要深拷贝,进行新的动态资源的开辟。

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;
    }
    Stack(const Stack& s) {
        DataType *tmp = (DataType*)malloc(s._capacity * sizeof(DataType));
        if (nullptr == tmp) {
            perror("new malloc fail!");
            exit(-1);
        }
        memcpy(tmp, s._array, sizeof(DataType) * s._size);
        _array = tmp;
        _size = s._size;
        _capacity = s._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;
}

;