目录
一:概念
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时,由编译器自动调用。即:用同类类型对象拷贝初始化
二:特征
1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。(传值传参时要调用拷贝构造函数)
class Date
{
public:
//构造
Date(int year = 0, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
//传值传参时,传自定义类型会调用拷贝构造
//如果不想调用拷贝构造,两种解决方法
//传地址(此时并不是拷贝构造,只是构造函数的一种重载形式)
Date(Date* d)
{
_year = d->_year;
_month = d->_month;
_day = d->_day;
}
//二:传引用(拷贝构造)
Date(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);//也是构造
Date d3(d1);//拷贝构造
Date d4 = d1;//拷贝构造的另一种写法
return 0;
}
用const修饰拷贝构造参数,是为了出现以下情况
//这样写编译器不会报错,但是顺序写反了,不仅不会完成拷贝,还会把原来的值改变了
Date(Date& d)
{
d._day = _day;
d._month = _month;
d._year = _year;
}
如果用来const修饰,上面的情况就会报错
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;
}
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的。
4.
编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了
,还需要自己显式实现吗?
当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
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;
}
由此可以看出如果是栈,不写显示拷贝构造,程序会报错
但是通过调试,我们又看到st2是拷贝成功的,那为什么最后会报错呢?
所以,类中如果没有涉及资源的申请时,拷贝是否写都可以;但是一旦涉及自愿申请时,则拷贝构造是一定要写,否则就是浅拷贝。
浅拷贝:按字节一个一个的拷贝
深拷贝:开一样大的空间,形状,空间均复制一样
typedef int SLType;
class Stack
{
public:
Stack(int n = 4)
{
SLType* tmp = (SLType*)malloc(sizeof(SLType) * n);
if (tmp == nullptr)
{
perror("malloc fail");
return;
}
_a = tmp;
_capacity = n;
_size = 0;
}
void Push(SLType x)
{
_a[_size++] = x;
}
//深拷贝
Stack(const Stack& st)
{
_a = (SLType*)malloc(sizeof(SLType) * st._capacity);
if (_a == nullptr)
{
perror("malloc fail");
return ;
}
_capacity = st._capacity;
_size = st._size;
memcpy(_a, st._a, sizeof(SLType) * st._size);
}
~Stack()
{
if (_a)
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
}
private:
SLType* _a;
int _size;
int _capacity;
};
int main()
{
Stack st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
Stack st2 = st1;
return 0;
}
5.拷贝构造函数典型调用场景
使用已经存在的对象创建新对象
函数参数类型为类类型
函数返回值类型为类类型
class Date
{
public:
Date(int year, int month, 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(2024, 4, 23);
Test(d1);
return 0;
}
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用
三:总结
1.如果没有管理资源,一般情况下不用写拷贝构造,默认生成的拷贝构造就可以,例如:Date
2.如果都是自定义类型,内置类型没有指向资源,也类似默认生成的拷贝构造,如MyQueue
3.一般情况下,不需要写析构函数就不需要写拷贝构造
4.如果内部有指针或一些指向资源,需要写析构函数,通常就需要写构造完成深拷贝,如:Stack List 二叉树