如果一个类中什么成员函数都没有,则称为空类,C++会自动生成6个默认成员函数,也称为缺省成员函数。
6个默认成员函数分为3种,分别为:
1). 初始化和清理:构造函数、析构函数
2). 拷贝函数、赋值函数
3). 取地址重载
1.构造函数:用来保证对象一定被初始化的函数
在C语言中我们经常用到结构体关键词struct,当我们用struct xxx 创建一个结构体变量时,这个结构体变量内部的一些参数往往是一些随机值,这些随机值如果我们忘记了初始化而直接使用,其结果是运行结果不对或程序报错。为了避免这件事的发生,在C++中引入了构造函数,它能够保证类中的一些变量会被初始化。
那么如何编写一个构造函数呢?
首先记住构造函数有如下特征:
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(2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
Date d3();
}
需要注意的是构造函数并不初始化内置类型成员,若还看见类中有内置类型成员被“赋值”这只是在给这个内置成员一个缺省值,并不是赋值,这是C++打的补丁。
2. 析构函数:当对象生命周期结束时,用来“清理善后”的函数。如释放内存等等
在实际使用中,我们用到的变量都是有生命周期的,当变量生命周期结束时往往还要做一些打扫清理的工作,最简单的,用malloc给变量开辟一块空间,当这个变量使用结束并不再使用时,按道理来讲这时应该将这个变量指向的空间给释放掉,不然一直这样下去最终内存泄漏到一定量时系统会崩溃。为了防止这种情况的发生,C++建立了析构函数,对于上述的情况就可以将析构函数的内容写成释放内存的代码。这样当变量调用完毕时,系统自动执行析构函数时有析构函数可执行,自然系统内存就释放了。
同理,如何编写一个析构函数呢?
首先要记住析构函数的特征:
1).析构函数名是在类名前加上字符 ~
2).无参数无返回值类型
3).一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4).对象生命周期结束时,C++编译系统系统自动调用析构函数
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
// 程序运行结束后输出:~Time()
// 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在
// d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数
// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函
// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
需要注意的是析构函数并不清理内置类型成员,系统会直接将其内存回收!
3. 拷贝构造函数:创建的实例化对象的对象名作为对象的参数
描述格式为:
一.原Data程序:
Data(形参...)
{
1......
2......
3......
}
二.拷贝构造函数实际使用格式:Data d2(d1),d1是Data类型
三.Data d2(d1)的原程序格式为Data (Data &d1):
Data (Data &d1)
{
1......
2......
3......
}
例子:
void func()
{
Data d1(2022,9,25);
Data d2(d1);//算拷贝成功
Data d3 = d1;//也算拷贝成功(是为了兼容C)
}
拷贝构造函数是构造函数的一个重载形式;拷贝构造函数的参数只有一个且必须是类型对象的引用,使用传值方式编译器直接存储,因为会引发无穷调用;在编译器默认生成的拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用拷贝构造函数完成拷贝的。
4. 运算符重载:创建一个函数,使自定义类型能够进行运算
一.运算符重载怎么使用:直接将两个类型变量放在一起进行加减乘除等运算就可以。
二.运算符重载源程序格式:类型 operator操作符(参数)
类型 operator操作符(参数1,参数2)
{
......
}
//第一个参数代表左操作数,第二个参数代表右操作数
//一般operator操作符(参数1,参数2)里面的参数用const修饰,即引用传参的地方只要不改变尽量用const修饰
//当某些程序需要访问private下面的元素时,不妨把“某些程序”放进类里面,如果显示参数过多是因为类里的类型函数有this指针,多了一个参数,此时把之前的形参多的删除即可,this会默认对应第一个参数
//cout << (d1=d2) << endl 实际是 cout << operator=(d1,d2) << endl
5.取地址运算符重载
了解即可
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; //年
int _month ; //月
int _day ; //日
};
取地址运算符重载是默认成员函数,我们不写编译器自动生成,自动生成就够用了,一般不需要我们写;只有特殊情况,才需要重载,比如想让别人获取到指定的内容!