在上一篇类与对象(一)中我们简单介绍了类,在面向对象编程的三大特性:封装、继承、多态中,封装即是将数据与代码捆绑在一起的过程,并对外部隐藏具体的实现细节。封装使得使用者不需要了解对象内部的复杂性,只需通过公共接口与之交互。这些公共接口包括但不限于默认成员函数。
默认成员函数
默认成员函数是类中由编译器自动生成的函数,它们在类的定义中没有显式声明时自动调用。
(当自己不写相应函数时,编译器调用默认成员函数,当自己写入相应函数时,默认成员函数不再被调用),对于以上六种默认成员函数,我们应该先学会它们的使用,再自己设计相应的成员函数。
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;
}
这样我们成功实现了需要深拷贝的拷贝构造函数。
可见当析构函数必须显式书写时,拷贝构造函数也需要显式书写。
总结:
介绍了六个默认成员函数中的前三个,需要我们掌握构造函数、析构函数、拷贝构造函数以及深浅拷贝的功能、特点,并且能独立书写出来。
看到这里不妨给博主点个赞和关注哟!!!