拷贝构造函数概念
只有单个形参,该形参是对本类型对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数的特征
1.拷贝构造函数时构造函数的一个重载形式
2.拷贝构造函数的参数只有一个且必须时类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷的递归调用
void Fun1(Date d)
{
}
void Fun2(Date& d)
{
}
int main()
{
Date d1(2022, 9, 22);
Fun1(d1);
Fun2(d1);
}
Fun1函数的形参只是实参的一个拷贝。(Date d(d1))会不断的调用拷贝构造,在传值的时候,编译器就直接禁掉了,报错。
Fun2函数的形参是实参的别名。
虽然传指针也是可以的,但是没有引用方便,况且规定也是参数要用引用;
内置类型是没有拷贝构造的概念的,自定义类型是有的。(因为编译器对内置知根知底,按字节拷没啥问题,但是对自定义的类型较为复杂的就是需要用函数来指示);
当然引用参数最好前面加一个const,防止拷贝的时候写反了,就有可能会把原来引用变量的数据给改了,所以加个const 把权限缩小一下,由写反的情况就会报错检查出来了。
3.如果没有显式的定义拷贝构造函数,编译就生成会默认的拷贝构造函数。
默认的拷贝构造函数,对内置的类型是按字节的方式直接拷贝的(类似memcpy),而自定义类型是调用其拷贝构造函数完成拷贝的。
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;
Date d2(d1);
return 0;
}
首先是d1的构造,后来是d2的拷贝构造,但是Date类没有显式写拷贝构造,所以默认生成一个拷贝构造,对于内置的年月日都是直接字节拷贝,但是对于自定义的Time,就是去调用Time的拷贝构造。
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进行复制构造,但是Stack并没有显式复制构造,那么编译器就生成一个复制构造,但是对Stack里面的都是直接的值拷贝,就连指针所指向的空间都是一样的,那么当最后创建的s2先析构的时候那块空间已经没了,但是s1那就是野指针了,再去析构就会报错。
所以这是浅拷贝,也叫值拷贝。
如果要避免以上的问题,就要进行深拷贝了,也就是把s2的指针开辟同样大的空间,地址和s1的指针不一样。
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
需要去写析构函数的类,就要去写深拷贝的拷贝构造。
不需要写析构函数的类,默认生成的浅拷贝就可以用。