一、类的6个默认成员函数
在一个类中,如果这个类什么都没有,那么这个类我们称之为空类,那么空类中真的什么都没有吗?其实并不是,任何类在什么都不写的情况下,编译器也会自动生成六个默认成员函数。
默认成员函数:用户没有显式实现,编译器会自动生成的成员函数被称为默认成员函数。
下面我们来详细介绍下这几个默认成员函数。
二、构造函数
2.1 构造函数的概念
我们来看下下面的一个Date类:
#include<iostream>
using namespace std;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(2022, 7, 5);d1.Print();Date d2;d2.Init(2004, 11, 12);d2.Print();return 0;
}
对于这个Date类,我们可以通过Init公有方法给对象设置日期, 但如果每次都用Init方法初始化的话比较麻烦,因此就出现了构造函数,构造函数能够将对象在创建的时候同时给创建出的对象进行初始化。
构造函数是一个特殊的成员函数,构造函数的函数名与类名相同,构造函数在创建类类型对象时,会由编译器自动调用,这样可以保障每个数据成员都有一个合适的初始值,而且构造函数在对象的整个生命周期内只能调用一次。
2.2 构造函数的特性
构造函数是特殊的成员函数,但是我们要知道,虽然构造函数的的名字叫做构造,它所做的事情并不是创建对象开空间,而是初始化对象。类似于Init函数的功能。
构造函数的特征如下:
1.函数名与类名相同
2.没有返回值
3.对象实例化时编译器自动调用对应的构造函数
4.构造函数可以重载
构造函数可以写多个,即有多种初始化方式:
我们来看下面几个构造函数的实例:
class Date
{
public://1.无参构造函数Date(){}//2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};void TestDate()
{Date d1;// 调用无参构造函数Date d2(2004, 11, 11);//调用带参构造函数}
注意:如果通过无参构造函数创建对象时,对象后面不用加括号,加了括号就变成函数声明了。
对于要调用带参数的构造函数时在创建对象的时候,在对象后面加上参数列表。
对于无参构造函数和带参的构造函数我们可以写成一个全缺省的带参构造,但是如果写了全缺省的代参构造后就不要再写无参的构造函数了,因为会产生调用歧义。
下面就是一个全缺省的带参构造的实例:
class Date
{
public://全缺省带参构造Date(int year = 2004, int month = 11, int day = 30){_year = year;_month = month;_day = day;}void Print(){cout << _year << " " << _month << " " << _day << endl;}private:int _year;int _month;int _day;
};void TestDate()
{Date d1;//使用缺省值Date d2(2004, 11, 11);//自己传参数d1.Print();d2.Print();}int main()
{TestDate();return 0;
}
5.若是用户没有显式的实现构造函数,那么C++的编译器会自动生成一个无参的默认构造函数,一但用户显式实现了构造函数那么编译器就不会再去实现。
我们来看下将我们自己实现的构造函数给去掉,编译器会怎么将数据初始化:
class Date
{
public:全缺省带参构造//Date(int year = 2004, int month = 11, int day = 30)//{// _year = year;// _month = month;// _day = day;//}void Print(){cout << _year << " " << _month << " " << _day << endl;}private:int _year;int _month;int _day;
};void TestDate()
{Date d1;//使用缺省值//Date d2(2004, 11, 11);//自己传参数d1.Print();/*d2.Print();*/}int main()
{TestDate();return 0;
}
可以看到这里我已经将我们刚才实现的构造函数给注释掉了,那么这段代码的运行结果会是怎样的呢?
从这个运行结果我们就可以看出,调用默认构造函数时是以随机值进行初始化的,看起来似乎这个默认构造没有太大的用途。
6.关于编译器生成的默认成员函数,很多人都会有这样的疑问,在我们不显式的实现构造函数时,编译器生成的默认构造函数看起来有没有什么用?
对于这个问题其实C++把类型分为内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,比如:int、char、double...等等,自定义类型就是我们使用的class、struct、union等自己定义的类型,我们来看下下面的程序,就会发现编译器生成的默认构造函数会对自定义类型成员调用它的默认成员函数,而对内置类型不做处理(这个看编译器的底层实现,有的编译器会进行处理,C++的标准里面没有规定)。
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}private:int _hour;int _minute;int _second;
};class Date
{
public:void Print(){cout << _year << " " << _month << " " << _day << endl;}
private://基本类型int _year;int _month;int _day;//自定义类型Time _t;
};int main()
{Date d1;d1.Print();return 0;
}
我们来看下这段代码的运行结果:
可以看到这里对自定义类型进行了处理,调用了time类的构造函数,但是对内置类型并没有处理。对于这个问题到了C++11的时候给出了一个补丁,即:内置类型成员变量在类中声明时可以给默认值。
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}private:int _hour;int _minute;int _second;
};class Date
{
public:void Print(){cout << _year << " " << _month << " " << _day << endl;}
private://基本类型int _year = 2004;int _month = 11;int _day = 13;//自定义类型Time _t;
};int main()
{Date d1;d1.Print();return 0;
}
我们在Date类中给我们的内置成员变量都赋予了一个初始值,我们来看下这次的效果:
可以看到这样的话就能够将内置类型默认构造的问题给解决了。
7.无参的构造函数和全缺省的构造函数都被称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数,全缺省构造函数和我们没显式实现的情况下编译器自动生成的默认构造函数,都是默认构造函数。
一般建议每一个类都写一个全缺省的构造函数。
总结:1.不用传参数就可以调用的函数就是默认构造函数
2.编译器自动生成的默认构造函数对于内置类型没有规定要处理
3.一般情况下构造函数都需要我们自己去显式的实现
4.只有少数的情况可以让编译器自动生成构造函数,类似myqueue的成员全是自定义类型。
三、析构函数
3.1 析构函数的概念
析构函数:与构造函数的功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的,而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作,析构函数的作用相当于Destroy的功能。
3.2 析构函数的特性
析构函数时特殊的成员函数,它的特征如下:
1. 析构函数名是是在类名的前面加上字符~
2. 没有参数,没有返回值类型
3.一个类里面只能有一个析构函数,若没有显式定义,编译器会自动生成默认的析构函数
4.析构函数不能重载
5.对象的声明周期结束时,C++编译系统会自动调用析构函数
对于析构函数我们先来看下下面的一个stack类:
typedef int DataType;
class Stack
{
public://全缺省的构造函数Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (_array == nullptr){perror("malloc申请空间失败!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){_array[_size] = data;_size++;}~Stack(){cout << "~Stack" << endl;if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}void Print(){cout << _capacity << endl;cout << _size << endl;}private:DataType* _array;int _capacity;int _size;};void TestStack()
{Stack s1; s1.Push(1);s1.Push(2);s1.Print();
}int main()
{TestStack();return 0;
}
可以看到这里程序运行结束之后自动调用了析构函数。
6.关于编译器自动生成的析构函数,能否完成一点事情呢,其实编译器自动生成的默认析构函数,对自定义类型成员会调用它的析构函数,对于内置类型成员销毁时不需要进行资源清理,最后系统会将其内存回收即可。
我们来看下下面的一个实例:
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}~Time(){cout << "~Time()" << endl;}private:int _hour;int _minute;int _second;
};class Date
{
public:void Print(){cout << _year << " " << _month << " " << _day << endl;}
private://基本类型int _year = 2004;int _month = 11;int _day = 13;//自定义类型Time _t;
};int main()
{Date d;return 0;
}
在这里我们看到调用Date的默认构造时调用了Time类的析构函数,因为_t是自定义类型,在d销毁时要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。
7.如果类中没有申请资源的话,析构函数可以不写,直接使用编译器默认生成的析构函数就行,比如Date类,在有资源申请的情况下,一定要写析构函数,否则可能会造成内存泄露,比如:stack类。
结语:
这篇博客到这里就结束了,这里我们介绍了类的默认成员函数中的构造函数和析构函数,希望大家通过这篇文章能够对构造函数和析构函数有一定的认识。