类的成员函数
类的默认六个成员函数
在对象的存储方式和this指针那节中,测试发现空类的大小为1,那么空类中真的什么都没有吗?
其实不是的,在类中什么都不写的时候,编译器会自动生成6个默认的成员函数。
默认的成员函数:当用户没有显式定义的时候,编译器会自动生成的成员函数。
构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
那么就有一个问题,为什么要设置这样的一个构造函数呢?
如果当前有这样一个Data类,这个类当中有一个Init初始化函数,若我们每次创建一个对象的时候,就必须都要调用这个Init函数对对象进行初始化,这样会非常麻烦,因此我们就需要有一个构造函数,使得我们在创建对象的时候,不需要调用任何函数,就可以实现对对象的初始化。
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(2022, 7, 6);
d2.Print();
return 0;
}
我们需要按照要求,去定义构造函数,函数名称和类名相同,函数不能有返回值,但是可以有不同的参数,从而可以实现重载,并且形参可以使用缺省参数。
class Date
{
public:
Date (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;
};
此外,当我们并没有显式定义构造方法,编译器会自动生成一个默认的构造方法,生成的默认构造方法有两个特点:
1.并不会初始化内置类型的变量
2.只会初始化自定义类型的变量(需要注意的是,自定义类型的变量所属的类必须有无参的构造方法,否则就会编译报错)
3.默认的构造方法是无参的
当用户显式定义了构造方法,那么编译器就不会再构造默认的无参构造方法了。
通过上述代码可以发现,当用户没有定义构造方法时,编译器自动定义的默认构造方法也并没有将变量初始化,还是一些随机值,是不是意味着,默认构造方法是没用的?
其实不是没用,类中定义的变量分为内置类型变量和自定义类型变量,默认的构造方法不会对内置类型变量进行初始化,但是默认的构造方法会调用自定义类型变量的类中的默认构造方法,从而对自定义类型变量进行初始化。
注意:::上述的默认构造方法可以是无参的构造函数,全缺省的构造函数,编译器默认生成的构造函数。
析构函数
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
那么,也会有同样的问题,我们为什么需要析构函数呢?
实际上,类中可能会有动态申请的内存空间,这些内存空间在对象销毁时如果没有进行释放,那么会造成内存泄漏,如果我们编写一个destroy函数,每次销毁对象时都需要调用这个函数,未免有些麻烦,而且容易忘记。
析构函数就是为我们解决了这个问题,当对象被销毁时,自动调用析构函数释放开辟的空间,防止内存泄漏。
析构函数有一些特点:
1.如果类中没有涉及到资源的管理,析构函数就不需要定义
2.如果涉及到了资源的管理,那么必须要进行析构函数的定义,否则就会出现内存泄漏(因为,用户如果没有定义析构函数,编译器生成的默认析构函数是无法进行资源的释放的)
析构函数没有参数,也没有返回值,不能重载,书写方式是~+类名
这样就会产生和构造函数一样的疑问,编译器生成的默认析构函数并不能对资源进行释放,那么是否意味着默认析构函数就没有用呢?
其实不是的,一个没有涉及到资源管理的对象,如果没有生成默认的析构函数,如果这个对象中存在一些自定义变量,这些自定义变量所属的类却涉及到了资源管理,那么当前类是无法去释放内部的那些自定义变量的资源的(因为当前类中没有其他类的析构函数,无法调用其他类的析构函数),这样岂不是造成内存泄漏了吗?
因此,就需要在当前类中默认生成一个析构函数,目的是在当前对象销毁时,调用析构函数完成内部一些对象的销毁工作
拷贝构造函数
拷贝构造函数:
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
在c语言中,如果需要创建一个变量,并且给变量赋值,有以下几种方式
int a;
a = 10;
int b = 20;
int c(b);
而在c++中,如果需要创建一个对象,也有以下几种方式
class Data
{
public:
int _year;
int _month;
int _day;
Data(int year = 2023, int month = 4, int day = 4)
{
_year = year;
_month = month;
_day = day;
}
};
Data d1;
d1._year = 2023;
d1._month = 4;
d1._day = 4;
Data d2(2023, 4, 5);
Data d3(d2);
对于前两种创建对象进行初始化的方式我们比较明白了,但是对于第三种方法,到底是如何将d3对象创建出来,并且将d2对象的值拷贝给d3的呢?
这就涉及到了拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型
对象创建新对象时由编译器自动调用。
这里就说明了,拷贝构造函数就是在编译器自动执行,实现创建出来的对象和已有的对象一模一样。
拷贝构造函数有几个特点
1.拷贝构造函数实际上是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个,必须要是类类型对象的引用,使用传值方式时编译器会直接报错,因为会引发无穷递归调用
当用户没有显式定义拷贝构造函数,默认生成的拷贝构造函数按内存存储字节序完成拷贝,而这种拷贝被称为浅拷贝,或者值拷贝。