类和对象(中)

(这块是重点)

1. 类的6个默认成员函数

在这里插入图片描述
如果一个类中什么成员都没有,则称为空类。但任何一个类在我们不主动实现的情况下都会自动生成6个默认成员函数。
(说白了默认成员函数就是,我们不写的话编译器会自动生成一份,我们写的话就用我们的。有些情况编译器默认生成的有问题,那我们就自己写,如果编译器默认生成的没问题,那我们就不用写了)

2. 构造函数

2.1 构造函数的概念

为什么要进行初始化?
如果不初始化的话就无法处理了,如下图
在这里插入图片描述
想要初始化的话,就要通过统一的方式,像下面这样

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

这样就可以在它的“监管”下操作了

int main()
{
	Date d1;
	d1.Init(2021, 3, 15);
	d1.Print();
	return 0;
}

但是上面还有这样一个问题,如果在调用Init函数之前先调用了Print,那就会出现随机值。所以我们想要的是,在函数定义(构造)出来时,就已经初始化了。而构造函数就是干这个事情的。

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。

Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//调用
Date d1(2021, 3, 15);

注意构造函数的调用和普通函数不一样,普通函数是在函数名之后加(),而构造函数是在对象后面加()

2.2 构造函数的特性

构造函数时特殊的成员函数,虽然名字时构造,但构造函数的主要任务不是开一块空间创造对象,而是初始化对象。

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数可以重载
    (也就是说可以写多个构造函数,可以有多种初始化的方式
//带参构造函数
    Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

//无参构造函数
	Date()
	{
		_year = 1900;
		_month = 1;
		_day = 1;
	}
//调用带参构造函数
Date d1(2021, 3, 15);
d1.Print();

//调用无参构造函数
Date d2;
d2.Print();

注意:通过无参构造函数创建对象时,对象后面不要加(),否则就成了函数声明了。如下面的代码

Date d3();

声明了d3函数,该函数无参,返回一个Date类的对象。

  1. 如果类中没有显式构造函数,那么C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义,那么编译器将不再生成。
  2. 构造函数也可以写成全缺省的构造函数
Date(int year = 2021, int month = 3, int day = 15)
	{
		_year = year;
		_month = month;
		_day = day;
	}
  1. 无参的构造函数和全缺省的构造函数都成为默认构造函数,并且默认构造函数只能有一个。
  2. 默认构造函数的区别对待

C++把类型分成内置类型(基本类型)和自定义类型。
内置类型就是语法已经定义好的类型,如int/char
自定义类型就是使用class/struct/union自己定义的类型

对于成员变量中的基本类型,默认构造函数什么事都不做。
对于成员变量中的自定义类型,会去调用它的默认构造函数(不传参数就可以调用的那个)

无参的构造函数、全缺省的构造函数、编译器默认生成的构造函数,都可以称为默认构造函数

3. 析构函数

3.1 析构函数的概念

与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁的工作是由编译器完成的。而对象在销毁时会自定调用析构函数,完成类的一些资源清理工作

(析构函数完成某些清理工作)

3.2 析构函数的特性
  1. 析构函数函数名是在类名前加上~
~Date()
{ 
}
  1. 无参数无返回值
  2. 一个类有且仅有一个析构函数。若没有显式定义,那么系统会自动生成默认的析构函数

像日期类中没有需要清理的资源,所以我们不用写析构函数,编译器会自动生成。
但像栈类,就需要我们手动写析构函数了

下面是栈类的析构函数

public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int)*capacity);
		_capacity = capacity;
		_size = 0;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_capacity = 0;
		_size = 0;
	}
  1. 对象生命周期结束时,C++编译系统自动调用析构函数
  2. 析构函数也像构造函数一样,区别对待。详情见上面的构造函数

构造函数和析构函数最大的好处就是可以自动调用,不会出现调用的时候忘记初始化,在最后的时候忘记销毁。

另外注意:构造顺序是按照语句的声明顺序进行构造,析构是按照构造的相反顺序进行析构。
且全局对象先于普通对象进行构造,静态对象先于普通对象进行构造

4.拷贝构造函数

4.1拷贝构造函数的定义
class Date
{
public:
	Date(int year = 2021, int month = 3, int day = 15)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{

	Date d1(2021, 3, 15);
	//Date d2???
	return 0;
}

如上面代码,d2的值想跟d1是一样的,应该怎么做呢?

第一种办法

Date d2(2021,3,15);

但是这样写的话,如果d1的值有改变,那么d2还得重新再改,很麻烦。
第二种方法就是用拷贝构造函数

拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用

代码如下

Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

//调用
Date d2(d1);
4.2 拷贝构造函数的特性
  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个(该函数参数是自身类型的对象的引用),并且必须使用引用传参,如果使用传值传参的话会发生无穷递归调用
    在这里插入图片描述
    否则就会这样在这里插入图片描述
Date(const Date& d)

这样就不存在把d1拷贝过来了,d是d1的别名(形参是实参的别名)
(另外注意:传指针或者引用的时候,如果不需要改变他,那就加上const)

  1. 若没有显式定义,系统生成默认的拷贝构造函数(自动生成的缺省拷贝构造函数为公有函数)。(默认的拷贝构造函数对象按内存存储字节序完成拷贝,这种拷贝叫浅拷贝或值拷贝)但像栈类这样的就需要深拷贝了
int main()
{
	Stack st1;
	Stack st2(st1);
	return 0;
}

栈 后定义的先调用,所以st2先调用析构函数,st1在调用析构函数
在这里插入图片描述
st1和st2在一个空间,如果st2先调用了析构函数,这块空间被free了,接着st1调用析构函数,但是这块空间已经free了,不能free两次。
像栈这种需要关注内存资源的类,浅拷贝是不能解决的,需要自己实现拷贝构造,弄成深拷贝(再弄一块独立空间,你干啥我就干啥,以后会详细说)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值