拷贝构造函数
如果⼀个构造函数的第⼀个参数是⾃⾝类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是⼀个特殊的构造函数。
拷⻉构造的特点:
- 拷贝构造函数是构造函数的⼀个重载。
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
int main()
{
Date d1(2024, 7, 12);
d1.Print();
Date d2(d1);
d2.Print();
return 0;
}
-
拷贝构造函数的参数第⼀个参数且必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。
-
C++规定⾃定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调⽤拷贝构造完成。
//void Func1(const Date& d)无外参数改变最好加一个const
void Func1(Date d)
{
cout << &d << endl;
d.Print();
}
// C++的规定,传值传参要调用拷贝构造
Func1(d1);
// error C2652: “Date”: 非法的复制构造函数: 第一个参数不应是“Date”
// Date d2(d1)
不加&号会出现错误
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
- 若未显式定义拷贝构造,编译器会⽣成自动生成拷贝构造函数。⾃动⽣成的拷贝构造对内置类型成员变量会完成值拷贝浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调调他的拷贝构造。
年月日作为内置函数 ,不需要拷贝构造函数,⾃动⽣成的拷贝构造对内置类型成员变量会完成值拷贝浅拷贝(⼀个字节⼀个字节的拷贝)。
int _year;
int _month;
int _day;
- 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显⽰实现拷贝构造。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的拷贝构造完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器自动生成的拷贝构造会调用Stack的拷贝构造,也不需要我们显示实现MyQueue的拷贝构造。这⾥还有⼀个小技巧,如果⼀个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。
//栈拷贝
typedef int STDataType;
class Stack
{
public:
Stack(int n = 4)
{
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
// st2(st1)
Stack(const Stack& st)
{
cout << "Stack(const Stack& st)" << endl;
// 需要对_a指向资源创建同样大的资源再拷贝值
_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败!!!");
return;
}
memcpy(_a, st._a, sizeof(STDataType) * st._top);
_top = st._top;
_capacity = st._capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
int main()
{
Stack st1;
// Stack不显示实现拷贝构造,用自动生成的拷贝构造完成浅拷贝
// 会导致st1和st2里面的_a指针指向同一块资源,析构时会析构两次,程序崩溃
Stack st2(st1);
//拷贝构造第二种写法
Stack st3=
return 0;
}
MyQueue
// 两个Stack实现队列
class MyQueue
{
public:
private:
Stack pushst;
Stack popst;
};
int main()
{
Stack st1;
st1.Push(1);
st1.Push(2);
func1(st1);
// Stack不显示实现拷贝构造,用自动生成的拷贝构造完成浅拷贝
// 会导致st1和st2里面的_a指针指向同一块资源,析构时会析构两次,程序崩溃
MyQueue mq1;
// MyQueue自动生成的拷贝构造,会自动调用Stack拷贝构造完成pushst/popst
// 的拷贝,只要Stack拷贝构造自己实现了深拷贝,他就没问题
MyQueue mq2(mq1);
return 0;
}
6.传值返回会产生⼀个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名(引用),没有产生拷贝。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于⼀个野引用,类似⼀个野指针⼀样。传引用返回可以减少拷贝,但是⼀定要确保返回对象,在当前函数结束后还在,才能用引用返回。
Stack func2()
{
Stack st;
return st;
}
int main()
{
Stack ret =func2();
return 0;
}
//传值传参,func2会先传到拷贝体中然后再从拷贝体里传回int
Stack& func2()
{
Stack st;
return st;
}
int main()
{
Stack ret =func2();
return 0;
}
//传引用,func2会直接返回st的别名,