c++:类和对象
构造函数和析构函数详解
`
文章目录
前言
拷贝构造,顾名思义就是复制.我们把一个已经创建自定义对象初始化创建新对象.
赋值运算符重载是把一个对象赋值给另一个对象,二者都是已创建的.
一、拷贝构造
怎么写拷贝构造
#include<iostream>
using namespace std;
class Date
{
public:
//构造函数,对自定义对象进行初始化
Date(int year = 2004, int month = 4, int day = 30)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
friend ostream& operator<<(ostream & out, Date & d);
private:
int _year;
int _month;
int _day;
};
//这个是<<的运算符重载
ostream& operator<<(ostream& out, Date& d)
{
cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
int main()
{
Date d1(2024,3,13);
Date d2 = d1;
cout << "d1:" << d1;
cout << "d2:" << d2;
return 0;
}
1.拷贝构造也是构造函数的一种,构造函数没有值.所以拷贝构造也没有返回值**
2.拷贝构造只有一个形参,正常这个形参是自定义类型对象的引用.
比如在Date这个类里面就是Date&.
为什么一定要是引用呢?我们都知道,函数的参数是一个形参,我们调用函数传过去的才是实参.而形参是实参的一个拷贝,我们要传值过去就必须创建一个形参.
我们传自定义类型Date的值时,因为要拷贝一个新对象,所以就要调用Date类型的拷贝构造.要是拷贝构造里面的参数也是Date的话,就会继续调用它的拷贝构造.如此形成无限递归.
传值调用拷贝构造编译器会自动报错
3. 如果我们没有显示写拷贝构造,编译器会自己生成.但是这种只是浅拷贝,在某些情况下面有危险
编译器默认的拷贝构造函数是按字节进行拷贝,在我们上面的日期类适用.因为我们没有动态开辟空间.
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
//动态开辟空间
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc fail");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
_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;
}
上面的代码在程序运行会崩溃
这是为什么?编译器不是会按字节去对内置类型进行拷贝吗?
结果调试,发现问题出现在free上,free被调用了两次.同一块空间被删除了两次.这才是导致问题出现的关键
第一次free
第二次free
编译器默认写的是浅拷贝,也叫值拷贝.它只会按字节去拷贝.在我们动态开辟空间时,浅拷贝不会开辟空间.这样子会导致两个对象的_array指针指向同一块空间.
自定义类型结束后会调用它的析构函数.因为有两个对象,所以析构两次,free两次.
Stack(Stack& st)
{
DataType* tmp = (DataType*)malloc(st._capacity * sizeof(DataType));
if (nullptr == tmp)
{
perror("malloc申请空间失败");
return;
}
memcpy(tmp, st._array, st._capacity* sizeof(DataType));
_array = tmp;
_size = st._size;
_capacity = st._capacity;
}
上面的类里面加上拷贝构造函数就不会报错,能够正常运行.这个也就是深拷贝.
二、赋值运算符重载
代码实现
`#include<iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
//构造函数,对自定义对象进行初始化
Date(int year = 2004, int month = 4, int day = 30)
{
_year = year;
_month = month;
_day = day;
}
//友元的声明
friend ostream& operator<<(ostream& out, Date& d);
//拷贝构造
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//赋值运算符重载
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
};
运算符重载
ostream& operator<<(ostream& out, Date& d)
{
cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
int main()
{
Date d1(2024,3,28);
Date d2;
cout << "d1:" << d1;
cout << "d2:" << d2;
d2 = d1;
cout << "d1:" << d1;
cout << "d2:" << d2;
return 0;
}`
赋值运算符重载和拷贝构造类似,本质还是对对象的拷贝.只不过拷贝构造是在对象初始化时运用的,赋值运算符是对已经存在的对象赋值
赋值运算符重载编译器默认生成的跟拷贝构造一样,都是浅拷贝
书写格式
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
1.operator是对内置操作符进行重载,如operator+
2.重载操作符必须有一个类型参数,最好用const修饰一下 , const Date&
3.形参总是比操作数少一(a=b有两个操作数),因为成员函数的第一个参数为this指针
4… :: sizeof ?: . 注意以上5个运算符不能重载.面试/笔试可能会考*
为什么返回值是Date&呢?
大家可能对这个问题比较好奇.
首先,我们的语言支持连等,我们可以同时给a,b,c三个变量赋值.
int main()
{
int a,b,c;
a = b = c = 10;
return 0;
}
当我们把上面赋值运算符重载的代码改成无返回值时,我们的连续赋值就会报错.
在底层实现d3=d2=d1时,会调用赋值运算符重载这个函数,因为赋值是从右往左过去,故将d1赋值给d2,如果没有返回值的话,就没有人来给d3赋值.程序就会崩溃.所以返回值必须是左操作数的引用.
void operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
}
总结
拷贝构造和赋值运算符是同一类成员函数.不主动写编译器会默认生成.但是生成的这个只是浅拷贝,在日期类这种没有分配资源的类可以用
但是在有开辟空间的类上不适用,要自己写.