首先我们先来大致了解类中的6个默认成员函数
当一个类中任何成员都没有的时候,我们就简称其为空类,但是我们要知道的是,空类当中也并不是什么都没有,任何一个类,在我们不去写的情况下,都会自动生成下面的6个默认成员函数。这就是类中的6个默认成员函数。
这6个默认成员函数可以分为以下三类:
- 初始化和清理
构造函数:完成初始化工作
析构函数:完成清理工作 - 拷贝复制
拷贝构造:使用同类对象初始化创建对象
赋值重载:把一个对象赋值给另一个对象 - 取地址重载
普通对象取地址
const对象取地址
接下来我们先来看其中的构造函数和析构函数
构造函数
先来看一个日期类:
#include <iostream>
using namespace std;
class Date
{
int m_year;
int m_month;
int m_day;
public:
void SetDate(int year, int month, int day)
{
m_year = year;
m_month = month;
m_day = day;
}
void Display()
{
cout << m_year << '-' << m_month << '-' << m_day << endl;
}
};
int main()
{
Date d1, d2;
d1.SetDate(2019, 12, 1);
d1.Display();
d2.SetDate(2019, 10, 1);
d2.Display();
system("pause");
return 0;
}
在这个Date类当中,我们可以通过SetDate这个公有的成员函数来给对象设置内容,但是如果每创建一次对象,都调用这个函数去设置信息,会显得有些麻烦,所以就考虑能不能在创建对象的时候,就将信息设置进去!
这就要用到构造函数了,构造函数其实是一个特殊的成员函数,它的名字与类名相同,在创建类类型的对象时由编译器自动调用,这样保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只会调用一次!
需要注意的是构造函数的名字叫做构造,但它的任务并不是开辟空间创建对象,而是初始化对象。
构造函数的特征
- 函数名与类名相同
- 没有返回值
- 对象实例化时编译器会自动调用对应的构造函数
- 构造函数可以重载
比如把我们上面的日期类改为下面这样:
#include <iostream>
using namespace std;
class Date
{
int m_year;
int m_month;
int m_day;
public:
//无参构造函数
Date()
{
}
//带参构造函数
Date(int year, int month, int day)
{
m_year = year;
m_month = month;
m_day = day;
}
void Display()
{
cout << m_year << '-' << m_month << '-' << m_day << endl;
}
};
int main()
{
Date d1; //调用无参构造
Date d2(2019, 1, 1); //调用带参的构造函数
//注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,不然就变成了函数声明
//比如下面这样,声明了d3函数,这个函数没有参数,返回值是个Date类型的对象。
Date d3();
system("pause");
return 0;
}
如果我们在类中没有显示定义构造函数那么我们的C++编译器会自动生成一个无参的默认构造函数,而当我们自己显式定义之后,编译器将不再生成。比如下面这样:
class Date
{
int m_year;
int m_month;
int m_day;
};
void Test()
{
Date d;
}
像这样没有定义构造函数,对象也是可以创建成功的,因为这里调用的是编译器生成的默认构造函数
无参的构造函数和全部缺省的构造函数都称为默认构造函数,并且一定要注意默认构造函数只能有一个
无参构造函数,全缺省构造函数,我们不去定义而编译器自己生成的构造函数,都可以认为是默认构造函数。
可以试试下面这段代码能否通过编译
#include <iostream>
using namespace std;
class Date
{
int m_year;
int m_month;
int m_day;
public:
Date()
{
m_year = 2019;
m_month = 12;
m_year = 1;
}
//带参构造函数
Date(int year = 2008, int month = 8, int day = 8)
{
m_year = year;
m_month = month;
m_day = day;
}
};
int main()
{
Date d1;
system("pause");
return 0;
}
析构函数
通过构造函数的学习,我们知道一个对象是怎么来的,那么一个对象又是怎么没的呢?
析构函数与构造函数功能相反,析构函数并不是完成对对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁的时候会自动调用析构函数,完成类的一些资源清理工作。
析构函数也是特殊的成员函数
析构函数的特征
- 析构函数名是在类名前加上字符~
- 没有参数返回值
- 每一个类都只有一个析构函数。如果我们没有显式定义析构函数,系统会生成默认的析构函数
- 对象的生命周期结束的时候,C++编译系统自动调用析构(一般在return 0 之后)
看下面这段代码,了解一下析构函数的使用:
typedef int DataType;
class SeqList
{
int * m_data;
size_t m_size;
size_t m_capacity;
public:
SeqList(int capacity = 10)
{
m_data = (DataType *)malloc(capacity * sizeof(DataType));
assert(m_data);
m_size = 0;
m_capacity = capacity;
}
~SeqList()
{
if (m_data)
{
free(m_data); //释放堆上空间
m_data = NULL; //将指针置为NULL,防止变为野指针
m_capacity = 0;
m_size = 0;
}
}
};
构造函数总结
- 它是一个特殊的成员函数,它不存在返回值,名字和类名相同,在实例化对象的时候自动调用.
- 系统会自动提供一个默认的构造函数,如果自己实现了构造函数(1),则系统不再提供默认的构造函数.
- 构造函数可以存在参数,它与其他的构造函数是以函数重载的方式共同存在的.
- 拷贝构造函数指的是参数为本类其他对象的引用的构造函数,它在给对象初始化成本类其他对象时调用,系统会自动提供一个拷贝构造函数
析构函数总结
- 析构函数是当一个栈被销毁前调用的,在C++中,当一个函数栈被销毁前,会调用栈中每一个对象的析构函数.
- 析构函数不存在参数也不存在返回值.它的名字是类名前加波浪线().外部写要写为类名::+类名.
- 系统会自动提供一个什么都不做的析构函数
关于浅拷贝和深拷贝
浅拷贝: 直接复制内存
深拷贝: 当内存成员中有指向堆的指针,就必须重新给该指针分配空间,然后将目标对象指针所指空间的内容拷贝到新分配的空间.(如果不这样做,会导致两个指针指向同一片空间,从而在析构中多次释放).