文章目录
- 1、拷贝构造函数的概念
- const用途
- 2、拷贝构造函数的特性
- 浅拷贝/值拷贝
前言:Hello,大家好,咱这篇博客继续默认成员函数,今天的笔记分享为拷贝构造函数~
1、拷贝构造函数的概念
在创建对象时,我们能否创建一个与已存在对象一某一样的新对象呢?
答案是可以的,让我们一起来看看吧。
首先我们先定义一个Date类:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 1, 28);
Date d2(d1);
return 0;
}
拷贝构造函数::只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。 像上述Date类中所含的拷贝构造函数:
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
上述拷贝构造函数我们也可以写成(加上this指针):
const用途
Date(Date& d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
当然了,一般情况下,我们还会加上const修饰:
Date(const Date& d)
正常情况下,加不加const无所谓,加上的目的主要是防止我们有时候写代码迷糊了,将构造函数写成如下形式:
d._year= this->_year;
d._month=this->_month ;
d._day=this->_day ;
这种情况相当于"逆拷贝",我们通过拷贝d2来改变d1,本末倒置。
const的出现就是为了避免此类"昏头行为",加上之后,当我们写反了编译器就会报错,我们就可以马上纠正了。
2、拷贝构造函数的特性
拷贝构造函数也是特殊的成员函数,其特性为:
a.拷贝构造函数是构造函数的一个重载形式(拷贝构造函数是构造函数的一种);
b.拷贝构造函数的参数只有一个且必须是类型为类的对象的引用,使用传值方式编译器直接报错(因为会引发无穷递归调用);
对于传值传参和传引用传参,我们通常作以下解释:
解释1.C++传值传参时需要调用拷贝构造函数:
解释2.但是传引用传参时,就不需要调用拷贝构造函数了,此时的rd就是d1的别名。
延伸3.对于以下这个拷贝构造函数,如果我们将它改为传值调用,会发生什么情况呢?
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
调用拷贝构造函数要先传参,而根据解释1,传值传参时需要调用拷贝构造函数,而且传值传参时会形成一个新的拷贝构造函数,不断调用,不断生成,最终会引发无穷递归。
也正因为如此,我们拷贝构造函数的参数必须是类型为类的对象的引用。
浅拷贝/值拷贝
c. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝;
class Date
{
public:
Date(int year = 1, 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, 1, 28);
Date d2(d1);
d1.Print();
d2.Print();
return 0;
}
这里我没写拷贝构造函数,但这里可以看出自动拷贝,也可以说明默认生成的拷贝构造函数对内置类型同样处理了(不同于构造、析构函数)。
d.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
当然像日期类这样的类是没必要的。但下面的类呢?让我们一起来看看吧。
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;
}
s2对象是s1使用拷贝构造,而此时Stack没有显示定义拷贝构造函数,所以s1中的内容会原封不动地拷贝到s2,此时就会出现一个问题:s1,s2指向了一个内存空间,当程序退出时,s1,s2都会调用析构函数来销毁,这就导致了一块内存空间会被释放两次,必将造成程序崩溃。
总结(重要):类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。