目录
拷贝构造函数
特性
自定义类型的传值传参和传引用传参对比
赋值运算符重载
拷贝构造函数
基本概念:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在创建一个已存在对象一模一样的新对象时由编译器自动调用
调用格式:类名 新对象(同类对象名)
类中格式: 类名(const 类名& 变量名)
作用:创建一个已存在对象一模一样的新对象
特性
1、拷贝构造函数是构造函数的一个重载形式(重名构成函数重载)
2、拷贝构造函数的传递的参数只有一个且类型必须是对象的引用,使用传值传参的方式会引发无穷递归调用,编译器报错
#include <iostream>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//正确写法:Date(Date& d)//最佳写法:Date(const Date& d)Date(Date d)//错误写法{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024,3,7);Date d2(d1);return 0;
}
自定义类型的传值传参和传引用传参对比
#include <iostream>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};//传值传参
void func1(Date d)//d是d1的拷贝
{}//传引用传参
void func2(Date& rd)//rd是d1的别名
{}int main()
{Date d1(2024,3,7);func1(d1);func2(d1);return 0;
}
调试过程:20240307_111203-CSDN直播
在调用func1函数前传参的过程中(d1对象的成员变量的数值会传递给d)会先进入拷贝构造函数中,执行完拷贝构造后才会调用func1函数:
在调用func2函数前传参的过程中不会进入拷贝构造函数,在传参结束后会直接调用func2函数:
这是因为C++规定,将一个对象作为参数传递给函数时(如果参数是按值传递而不是引用)会调用拷贝构造函数,如果我们不额外的写这两个函数来找寻找传值调用和传参调用的区别而是直接将拷贝构造函数写成Date(Date A):
就会造成无限递归,由于我们想要用d1对象中的成员变量的值去初始化d2对象的成员变量的值,在传值传参时d1传递给拷贝构造函数的是一个它值(所有成员变量的值)的临时拷贝,这个值也可以被视为一个新的对象e1,想要做的是初始化这个新的对象然后将初始化的结果带回去,带不带的回去还是一说(传值调用,形参是实参的拷贝,对形参的改变不会影响实参),当我们尝试初始化这个对象时又会调用拷贝构造函数,此时又会生成e1的拷贝,新对象f1......
结论:直接记住拷贝构造函数的格式即可,当你尝试使用传值传参时,编译器会报错
调用函数时先传参后调用
#include <stdio.h> int func(int a) {return a; }int main() {func(5);return 0; }
3、拷贝构造函数记得引用前要加const,const可以很好的保护被引用的对象(d1对象的值不能被d2对象的值初始化)
否则出现原本是想借用拷贝构造函数初始化新对象的成员变量,但是赋值两端的内容写反了,新对象的成员变量(随机值)反而把拷贝构造函数的成员变量初始化成随机值了
d2(d1)看起来只是将对象d1作为实参传递了,但实际上还有一个包含d2对象地址的this指针
4、若为显示定义拷贝构造函数,编译器会生成默认的拷贝构造函数,默认拷贝构造函数会对内置类型成员变量会进行值拷贝(将成员变量的字节依照原有的顺序一个一个的拷贝)也叫浅拷贝
#include <iostream>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}//没有显式定义拷贝构造函数
private:int _year;int _month;int _day;
};int main()
{Date d1(2024,3,7);Date d2(d1);d1.Print();d2.Print();return 0;
}
5、 拷贝构造函数也是构造函数
#include <iostream>
using namespace std;class Time
{
public:~Time(){cout << "~Time()" << endl;}//强制编译器生成默认构造函数: Time() = default;Time(const Time& t){cout << "Time(const Time& t)" << endl;_hour = t._hour;_minute = t._minute;_mecond = t._mecond;}
private:int _hour;int _minute;int _mecond;
};class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private://内置类型int _year;int _month;int _day;//自定义类型Time _t;
};int main()
{Date d1(2024,3,7);Date d2(d1);d1.Print();d2.Print();return 0;
}
由于我们在Time类中提供了拷贝构造函数所以编译器不会生成默认构造函数,对此我们可以使用default关键字强制编译器生成默认构造函数
6、类中没有涉及资源申请时,拷贝构造函数是否写都可以,一旦涉及资源申请,则拷贝构造函数一定要写否则就是浅拷贝
#include <iostream>
using namespace std;
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对象要用s1对象的成员变量进行初始化,Stakc类没有显示定义拷贝构造函数,编译器会生成一份默认的拷贝构造函数,默认拷贝构造函数是按值拷贝的,即将s1中的内容原封不动的拷贝到s2中,对于_size和_capacity没问题,但是_array存放的可是s1在堆上开辟的空间的地址,此时将该地址的值也原封不动的传递给了s2的_array,此时s1和s2对象的_array指向同一片空间,当main函数结束时,s2先销毁,s2销毁时调用析构函数释放掉申请的空间,由于s1不知道,所以会将该空间再次释放,同一块内存空间的多次释放肯定会造成程序崩溃
因此对于申请了空间资源的类我们要进行深拷贝:
#include <iostream>
using namespace std;
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(const Stack& s){DataType* tmp = (DataType*)malloc(s._capacity * (sizeof(DataType)));if(tmp == nullptr){perror("malloc fail");exit(-1);}memcpy(tmp, s._array, sizeof(DataType) * s._size);_array = tmp;_size = s._size;_capacity = s._capacity;}//析构函数~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2(s1);return 0;
}
~over~