目录
拷贝构造函数
定义铺垫
浅拷贝
深拷贝
总结
拷贝构造函数
那在创建对象时,可否创建一个与一个对象一某一样的新对象呢?
定义铺垫
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象 创建新对象时由编译器自动调用。
拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
- 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷 贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
- 那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像 日期类这样的类是没必要的
class Date
{
public:Date(){_year = -1;_month = -1;_day = -1;}Date(int year, int month, int day){_year = year;_month = month;_day = day;}Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;cout << &_year << "-" << &_month << "-" << &_day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 4, 18);d1.Print();Date d2(d1);d2.Print();return 0;
}
在这里我们给一段代码。
其中要注意这里的拷贝构造是引用传参
我们发现他们的打印结果相同 地址也相同
这就是拷贝函数
浅拷贝
class Date
{
public:Date(){_year = -1;_month = -1;_day = -1;}Date(int year, int month, int day){_year = year;_month = month;_day = day;}Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;cout << &_year << "-" << &_month << "-" << &_day << endl;}
private:int _year;int _month;int _day;
};
void func(Date d)
{d.Print();
}
int main()
{Date d1(2024, 4, 18);func(d1);return 0;
}
然后我们在进行调试
我们会发现当我们的d1初始化完后,我们下一步按F11时,会先进入Date的拷贝构造中,然后再调用func函数
所以
调用func得先传参,自动以类型对象传值传参要调用拷贝构造
当然了我们也可以不去使用拷贝构造
比如使用指针或者使用引用
void func(Date& d)
{d.Print();
}
void func(Date* d)
{d.Print();
}
一个是d1的地址一个是d1的别名
在这里我们还可以继续更改一下代码
Date(const Date& d){this->_year = d._year;this->_month = d._month;this->_day = d._day;}
加上一个const去缩小权限
因为我们只是拷贝构造不需要改变值
这里也是存在this指针的
Date(const Date& d){this->_year = d._year;this->_month = d._month;this->_day = d._day;}
拷贝构造的形式有很多
int main()
{Date d1(2024, 4, 18);Date d2 = d1;Date d3(d2);return 0;
}
这种类似于赋值的形式也是拷贝构造
class Date
{
public:Date(){_year = -1;_month = -1;_day = -1;}Date(int year, int month, int day){_year = year;_month = month;_day = day;}/*Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}*/void Print(){cout << _year << "-" << _month << "-" << _day << endl;cout << &_year << "-" << &_month << "-" << &_day << endl;}
private:int _year;int _month;int _day;
};
void func(Date d)
{d.Print();
}
int main()
{Date d1(2024, 4, 18);Date d2 = d1;Date d3(d2);d2.Print();d3.Print();return 0;
}
我们把我们的拷贝构造给注释掉
然后在进行打印会发现依旧会进行拷贝构造
原因是拷贝构造是默认的成员函数,不写他会进行值的拷贝,简称值拷贝
深拷贝
但如果是栈呢?
struct stack
{
public:int* a;int size;int capacity;void Init(int n = 4){a = (int*)malloc(sizeof(int) * n);if (a == nullptr){perror("malloc fail");return;}//...size = 0;capacity = n;}void Push(int x){a[size++] = x;}
};
int main()
{/*Date d1(2024, 4, 18);Date d2 = d1;Date d3(d2);d2.Print();d3.Print();*/stack st;st.Push(1);st.Push(1);st.Push(1);stack st1 = st;return 0;
}
我们会发现代码崩溃了
但拷贝是否完成了呢?
拷贝完成了
我们这里也是完成了值拷贝
这里会存在一个大问题
如果只进行了值拷贝,也就是说这两个栈都是使用的同一块内存,如果进行析构的话会进行两次,出现错误,所以只要是存在浅拷贝/值拷贝,就会可能出现一定的错误
所以,浅拷贝的类我们可以进行值拷贝,但如果是深拷贝就需要慎重考虑了
所以这里怎么办呢?
我们可以在构造函数时进行一定的改进
struct stack
{
public:/*void Init(int n = 4){a = (int*)malloc(sizeof(int) * n);if (a == nullptr){perror("malloc fail");return;}size = 0;capacity = n;}*/void Push(int x){a[size++] = x;}stack(const stack& st){a = (int*)malloc(sizeof(int) * st.capacity);if (a == nullptr){perror("malloc fail");return;}memcpy(a, st.a, sizeof(int) * st.size);size = st.size;capacity = st.capacity;}stack(){a = (int*)malloc(sizeof(int) * capacity);if (a == nullptr){perror("malloc fail");return;};size = capacity = 4;}~stack(){a = nullptr;size = capacity = 0;}
private:int* a = nullptr;int size = 4;int capacity = 4;
};
int main()
{/*Date d1(2024, 4, 18);Date d2 = d1;Date d3(d2);d2.Print();d3.Print();*/stack st1;st1.Push(1);st1.Push(1);st1.Push(1);stack st2 = st1;return 0;
}
这样我们的代码就可以正常运行拷贝构造了
总结
总结一下:
-
如果没有管理资源,一般情况下不需要写拷贝构造,默认生成的拷贝构造就可以,如:Date
- 如果自定义类型的话,内置类型没有指向资源,也类似默认生成的拷贝构造就可以
- 一般情况下,不需要显示写析构函数,就不需要写拷贝构造
- 如果内部有指针或者有一些只想资源,需要显示写析构函数,通常就需现实些构造完成深拷贝。如:stack,queue