类与对象(二)————构造与析构函数

  在上一篇类与对象(一)中我们简单介绍了类,在面向对象编程的三大特性:封装、继承、多态中,封装即是将数据与代码捆绑在一起的过程,并对外部隐藏具体的实现细节。封装使得使用者不需要了解对象内部的复杂性,只需通过公共接口与之交互。这些公共接口包括但不限于默认成员函数。

默认成员函数

默认成员函数是类中由编译器自动生成的函数,它们在类的定义中没有显式声明时自动调用。

(当自己不写相应函数时,编译器调用默认成员函数,当自己写入相应函数时,默认成员函数不再被调用),对于以上六种默认成员函数,我们应该先学会它们的使用,再自己设计相应的成员函数。

1.构造函数---负责类的初始化

特点:

1.构造函数函数名与类名相同

2.无返回值、无void

3.自动调用

4.允许重载

在构造函数中有一类被规定为默认构造函数——无需传参即可调用。(无参构造函数、全缺省构造函数、编译器自带的函数均被称为默认构造函数)

以日期类实现:

class Date
{
public:
// 函数接口
private:
  int year;
  int month;
  int day;
};

写出三种构造函数:(以后还会有拷贝构造函数)

//无参构造
Date()   
{
     //未进行初始化,和编译器自带的函数一样
	_year;
	_month ;
	_day ;
}
//半缺省构造
Date(int year,int month=10,int day=11)
{
	//必须传1个参数作为_year的值
	_year = year;
	_month = month;
	_day = day;
}
//全缺省构造
Date(int year = 2004, int month = 10, int day=1)
{
	//可以完全不传参,缺省值即为初始值。也可以依次传参
	_year = year;
	_month = month;
	_day = day;
}

加上编译器自带的构造函数,这些构造函数只要有一个即可,如果有两个存在,代码会有二义性。

系统不知道调用哪个而报错。

2.析构函数---销毁类中资源

负责类中的资源销毁(指的是向堆申请的空间),而不是对类本身销毁。

内置类型有自己的析构函数,而只有向堆申请空间的自定义类型才必须写析构函数释放空间。

简而言之,如果没有向内存申请空间,就不需要写析构函数。

特点:

1.与构造函数一样,函数名与类名相同,但之前要加 ~

2.无参 无返回值 无void

3.一个类只能有一个析构函数

4.类的对象生命周期结束,系统自动调用

实现:

我们给出一个必须写析构函数的类stack

class stack
{
public:
	stack()
	{
		_ptr = new int[10];
		_size = 0;
		_capacity=10;
	}
	~stack()
	{
		delete[] _ptr;
		_size = 0;
		_capacity = 0;
		cout << "析构结束" << endl;
	}
private:
	int* _ptr;
	int _size;
	int _capacity;
};

   如果同时构造多个对象,它们的析构顺序应该是:后构造的先析构。

3.拷贝构造函数

功能:

用实例化对象初始新对象

先看下面一段代码

int a = 678;
int b = a;
cout << a << '\n' << b;

先构造了a,又用a来拷贝构造了b,那对于类是不是也有相同的操作呢?

答案是肯定的,因为编译器会提供默认拷贝构造函数。

就像这样,我们用一个日期类对象来拷贝构造另一个对象。(注:这里<<流插入运算符已经被重载,在下一篇博客中会介绍)

Date a(2007, 8, 16);
cout << a << endl;
Date b = a;
cout << b << endl;

实现:

如果让我们自己写出拷贝构造函数:

拷贝构造也是构造函数,要满足构造函数的特点

Date(const Date& a)  //用const防止引用对象被改变
{
	_year = a._year;
	_month = a._month;
	_day = a._day;
}

到这里,拷贝构造还没有结束。

在前文我们展示析构函数时,以stack类为例。那编译器自带的默认拷贝构造函数对stack类还适用吗?我们尝试一下。

class stack
{
public:
	stack()
	{
		_ptr = new int[10];
		_size = 0;
		_capacity=10;
	}
	~stack()
	{
		delete[] _ptr;
        _ptr=nullptr;
		_size = 0;
		_capacity = 0;
		cout << "析构结束" << endl;
	}
private:
	int* _ptr;
	int _size;
	int _capacity;
};
int main()
{
	stack a;
	stack b = a;

}

上面这段代码运行时会报错,那为什么自带的拷贝构造函数对Date 类适用,对stack类却不适用了呢?这两个类的区别就在于向内存申请空间

解释:

原来,在默认拷贝构造函数中,编译器采取浅拷贝,即将一个对象中所有类型拷贝复制一份给新对象,这对Date类是允许的,构造析构都正常。但对于stack类,将_ptr复制给新对象中的_ptr(我们给它取一个新名字:qtr)浅拷贝之后,_ptr=qtr,但指针存储的是地址,构造时还不会出错,析构时,_ptr先销毁,这时指向的空间已经销毁了,再销毁qtr时就会报错。因此,默认的拷贝构造函数不再适用,我们需要自己手动写拷贝构造。对_ptr这种申请空间的类型浅拷贝不能使用,要用到深拷贝

写出stack类的拷贝构造函数:

stack(const stack& a)
{
	_ptr = new int[10];
	*_ptr = *a._ptr;
	_size = a._size;
	_capacity = a._capacity;
}

这样我们成功实现了需要深拷贝的拷贝构造函数。

可见当析构函数必须显式书写时,拷贝构造函数也需要显式书写。

总结:

介绍了六个默认成员函数中的前三个,需要我们掌握构造函数、析构函数、拷贝构造函数以及深浅拷贝的功能、特点,并且能独立书写出来。

看到这里不妨给博主点个赞和关注哟!!!

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值