目录
1、类的6个默认类成员函数
2、构造函数
2.1 概念
2.2 特性
3、析构函数
3.1、概念
3.2、特性
1、类的6个默认类成员函数 如果一个类中什么成员都没有,简称为空类,空类中什么都没有吗?并不是的,任何一个类在我们不主动写的情况下,编译器都会自动生成下面6个类成员函数,称之为默认类成员函数:
class Date {};
一般情况下,类成员变量均设置为私有或保护,而类成员函数则要分类讨论,若想开源的话就设置成公用,若不想开源的话,就设置成私有或保护,但大多数情况下,类成员函数都会设置成公用的,所以在此处的默认类成员函数均以设置成公用为例,即,无论是自己显式的在类体中实现的还是我们不写编译器自动生成的,都是公有的、
注意:此处所说的类的6个默认类成员函数中均包含有this指针、
2、构造函数
2.1 概念
对于以下的日期类:
#define _CRT_SECURE_NO_WARNINGS 1 #include对于Date类,可以通过类成员函数Init公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息,未免有点麻烦,那么能否在对象创建时,就将信息设置进去呢? 构造函数是一个特殊的类成员函数,构造函数的函数名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次、在C++中添加了构造函数后,则上述代码就不再需要类成员函数Init了、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, 5, 15); //若此处不进行初始化,则直接打印出来的就是随机值,或者在初始化之前就进行了打印的操作,也会出现随机值、 //除此之外,未初始化可能在一些情况下会导致程序崩溃掉,忘记初始化操作是一件非常正常的事情,所以在C++中添加了构造函数来弥补这一缺陷、 d1.Print(); return 0; }
2.2 特性
构造函数是特殊的类成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象、
其特征如下:
1、构造函数的函数名与类名相同、
2、构造函数没有返回值、
3、对象实例化时编译器自动调用对应的构造函数,并且只会调用一次、
4、构造函数可以重载,一个类体中可以存在多个构造函数,只要能够保证构成重载就行,不管是 无参的,带参全缺省的,带参半缺省的,只要满足重载,都是可以的,但通常情况下,只写一 个带参全缺省的即可解决问题了、
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
class Date
{
public:
//构造函数、
Date()
{
_year = 2022;
_month = 7;
_day = 7;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day; //日
};
int main()
{
Date d1;
d1.Print(); //2022-7-7
return 0;
}
在C语言中,若调用函数不写返回类型的话,则默认的是整型int类型,此时,当写return时,只能返回整型int类型的数据,而在C++中,普通的调用函数若不写返回类型的话,就会报错,但是,C++中的类里的构造函数是不存在返回值的,构造函数不写返回类型是不会默认为int类型的,构造函数的返回类型也不能写void类型,必须空着,在构造函数的函数体内可以存在return,但是不能返回任何数据,这是因为构造函数不存在返回值,注意不要混淆,当我们在类体内定义或声明函数的时候,该函数的函数名一定不可以与默认类成员函数的函数名相同、
#define _CRT_SECURE_NO_WARNINGS 1 #includeusing namespace std; class Date { public: //构造函数可以重载、 Date() // 1.无参构造函数、 { _year = 2022; _month = 7; _day = 7; } Date(int year,int month,int day) // 2.带参构造函数、 { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year;//年 int _month;//月 int _day; //日 }; int main() { Date d1; //调用无参构造函数,此时对象的后面没有参数,当调用无参构造函数时,只能写成此时这种情况,不能写成 Date d1(); //否则编译器会报错说是未定义对象,因为编译器无法区分到底是在调用无参构造函数还是在进行函数的声明,此处报错的原因是编译器把 //该操作看做是函数的声明了,则就没有进行对象的定义,所以在执行 d1.Print(); 时就会报错、 d1.Print(); //2022-7-7 Date d2(2022,7,7); //调用带参的构造函数,此时参数跟在对象的后面、 d2.Print(); return 0; }
构造函数的存在就保证了实例化出来的对象一定会进行初始化,因为在实例化对象的过程中,编译器就会自动的调用构造函数、
5、如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,且该无参的默认构造函数的函数体为空,一旦用户显式定义构造函数的话,则编译器将不再自动生成、
6、无参的构造函数和全缺省的带参构造函数都称为默认构造函数,并且默认构造函数只能有一个、
注意:无参构造函数、全缺省的带参构造函数、我们没写而编译器自动生成的构造函数,都可以认为是默认构造函数。
注意:并不是只有我们不写,编译器自动生成的构造函数才叫做默认构造函数,而是我们不需要传参数(实参),编译器就 能够 自动调用的都看做是默认构造函数,在此要注意,默认构造函数和前面所讲的默认类成员函数的概念要区分开,不要混淆。
#define _CRT_SECURE_NO_WARNINGS 1 #includeusing namespace std; class Date { public: //构造函数可以重载、 Date() // 1.无参构造函数、 { _year = 2022; _month = 7; _day = 7; } Date(int year=1, int month=1, int day=1) // 2.带参构造函数(全缺省,写成半缺省也是可以构成函数重载的)、 { _year = year; _month = month; _day = day; } //上述两者能够构成函数重载,在语法上是可以的,能够构成函数重载,可以成功编译、 //通过main函数中的分析可得,当使用构造函数时,只需要写一个全缺省的带参构造函数即可解决问题,当然,我们也可以根据具体情况使用半缺省。 如下所示: //Date(int year = 1, int month = 1, int day = 1) //全缺省的带参构造函数、 //{ // _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.Print(); return 0; }
比如下面的代码中程序就会报错:
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //报错,没有合适的默认构造函数可以使用、
return 0;
}
此时程序就会出现问题,报错为:没有合适的默认构造函数可以使用、
因为我们自己显示的在类体中定义了构造函数(非默认构造函数),所以编译器将不再自动生成无参的默认构造函数,此时就会报错,除非修改为:Date d1(2022, 5, 15);
//三种默认构造函数、
//1.没写时编译器默认生成的无参构造函数、
类名()//实际不可见、
{
//1.对于内置类型的类成员变量不进行处理、
//2.对于自定义类型的类成员变量会自动调用其构造函数(默认和非默认)来进行初始化、
}
//2.无参构造函数、
类名()
{
//可以对内置类型的类成员变量进行初始化、
_year = 0;
_month = 0;
_day = 0;
//即使在该无参构造函数的函数体内不对自定义类型的类成员变量进行处理,但编译器也是可以自动对自定义类型的类成员变量通过自动调用其构造函数(默认和非默认)进行初始化,就是在所谓的初始化列表中对他进行了处理、
}
//3.全缺省带参构造函数、
类名(int year = 0, int month = 0, int day = 0)
{
//可以对内置类型的类成员变量进行初始化、
_year = year;
_month = month;
_day = day;
//即使在该全缺省带参构造函数的函数体内不对自定义类型的类成员变量进行处理,但编译器也是可以自动对自定义类型的类成员变量通过自动调用其构造函数(默认和非默认)进行初始化,就是在所谓的初始化列表中对他进行了处理、
}
7、关于编译器自动生成的无参的默认构造函数,很多人会有疑惑:在类体中,我们不显示的实现构造函数(包括默认的和非默认的构造函数)的情况下,编译器会自动的生成默认的无参构造函数,如果类体中只有内置类型的类成员变量的话,则此时编译器自动生成的无参的默认构造函数看起来并没有起到什么作用,如下所示,对象d1调用了编译器自动生成的无参的默认构造函数,但是对象d1的_year/_month/_day,依旧是随机值,也就说在这里编译器自动生成的无参的默认构造函数并没有什么用?
#define _CRT_SECURE_NO_WARNINGS 1 #includeusing namespace std; class Date { public: void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; d1.Print(); //随机值、 return 0; }
解答:
C++把类型分成内置类型(基本类型)和自定义类型,所谓内置类型就是语法已经定义好的类型:如 int/char/double…等和 所有的指针类型,自定义类型就是我们使用关键字:使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器自动生成的无参的默认构造函数对于内置类型的类成员变量不做处理,而对于自定义类型的类成员变量才会处理、
#define _CRT_SECURE_NO_WARNINGS 1 #includeusing namespace std; class Time { public: Time() //默认构造函数 { _hour = 0; _minute = 0; _second = 0; } int _hour; int _minute; int _second; }; class Date { public: //当我们在Date类内不显示的写构造函数(默认和非默认的)的时候,编译器会自动生成一个无参的默认构造函数、 //内置类型/基本类型:int/char/double../ 所有的 指针类型、 自定义类型:由关键字class/struct/union..等自己定义的类型、 //编译器自动生成的无参的默认构造函数对于内置类型的类成员变量不会做处理,而这三者在Date类里都是声明,在实例化对象d1时才会定义,这三者均属于对象d1, //而对象d1又是局部的,故这三者的数值打印出来就是随机值。 //此时,类成员变量_time的类型是自定义类型,所以会被编译器自动生成的无参的构造函数进行处理,具体处理方法就是: 自动调用该自定义类型的类成员变量的 构造函数 进行处理的, //此处所讲的构造函数包括默认构造函数和非默认构造函数,目前而言,我们只考虑调用该自定义类型的类成员变量的 默认构造函数 来进行处理,至于调用该自定义类型的类成员变量 //的 非默认构造函数 的方法在后面的 初始化列表 再进行阐述、 //如果把Time类中的默认构造函数改为非默认构造函数的话,而在Date类中,仍执行Time _time; 的话,那么也是 自动调用 该自定义类型的类成员变量_time的构造函数进行初始化, //但是由于把Time类中的默认构造函数改为非默认构造函数,所以,就会报错说是不存在合适的默认构造函数可以使用,故,自动调用不成功,则此时若想在Time类体中换为非默认构造函数后的基础上成功运行的话,就必须对Date类中进行部分修改操作,具体方法在后面的初始化列表中会有讲解、 void Print() { cout << _year << "->" << _month << "->" << _day << endl; cout << _time._hour << "->" << _time._minute << "->" << _time._second << endl; } private: //内置类型的类成员变量、 int _year; int _month; int _day; //自定义类型的类成员变量、 Time _time; //只是声明,并不是在实例化对象(定义)、 }; int main() { Date d1; d1.Print(); //-858993460-> - 858993460-> - 858993460 //0->0->0 return 0; } //若在类体中不显示的实现构造函数(默认和非默认)的话,编译器会自动生成一个无参的默认构造函数,如果类体中存在内置类型的类成员变量的话,此时编译器自动生成的无参的默认构造函数 //就不能够处理这些内置类型的类成员变量,所以就无法进行正确的初始化,那么此时就需要自己手动的在类体中显示的实现构造函数(默认和非默认)来对这些内置类型的类成员变量进行初始化, //而不使用编译器自动生成的无参的默认构造函数,除此之外,若需要显示传参(实参)进行初始化的话,此时也需要在类体中显示的实现构造函数(默认和非默认),而不使用编译器自动生成的无参的 //默认构造函数,当某一个类体中只有自定义类型的类成员变量的话,此时直接使用编译器自动生成的无参的默认构造函数就可以完成初始化了,就不再需要手动的在类体中显示的实现构造函数(默认和非默认)了、
注意: 若在类体中不显示的实现构造函数(默认和非默认)的话,编译器会自动生成一个无参的默认构造函数,如果类体中存在内置类型的类成员变量的话,此时编译器自动生成的无参的默认构造函数就不能够处理这些内置类型的类成员变量,所以就无法进行正确的初始化,那么此时就需要自己手动的在类体中显示的实现构造函数(默认和非默认)来对这些内置类型的类成员变量进行初始化,而不使用编译器自动生成的无参的默认构造函数,除此之外,若需要显示传参(实参)进行初始化的话,此时也需要在类体中显示的实现构造函数(默认和非默认),而不使用编译器自动生成的无参的默认构造函数,当某一个类体中只有自定义类型的类成员变量的话,此时直接使用编译器自动生成的无参的默认构造函数就可以完成初始化了,就不再需要手动的在类体中显示的实现构造函数(默认和非默认)了、 总结: 一般情况下,一个C++类中,都要自己写(默认和非默认)构造函数,一般只有少数情况可以让编译器自动生成无参的默认构造函数:注意:看下面的例子:1、类体中都是自定义类型的类成员变量,并且这些类成员变量都提供了默认构造函数(以默认的为主,非默认的话需要初始化列表,要是使用编译器自动生成的无参的默认构造函数仍然对内置类型的类成员变量不进行处理)、
2、如果存在内置类型的类成员变量,并且这些内置类型的类成员变量在类体中声明时给了缺省值,也可以使用编译器自动生成的无参的默认构造函数,但是这种方法不灵活,要想改变内置类型的类成员变量的值的话只能通过改变这些内置类型的类成员变量在类体中声明时所给的缺省值,所以该方法不够灵活,当然这种情况相对来说比较少、
3、使用C++库里的STL数据结构时可以使用编译器自动生成的无参的默认构造函数、注意:构造函数必须定义在public权限中,否则我们无法在主函数中(类外)进行访问、
class Time
{
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date()
{
_year = 0;
_month = 0;
_day = 0;
_time._hour = 0;
_time.minute = 0;
_time.second = 0;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
cout << _time._hour << '-' << _time._minute << "-" << _time._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _time;
};
在上面的例子中,我们试图在Date类中的构造函数中将_time._hour、_time._minute、_time._second进行初始化,但这是无法实现的,因为在Time类中,我们将这三个类成员变量定义为私有权限,在类外无法进行访问,所以自然也就不能在Date类中的构造函数中进行初始化、
结论:各个类中应当有自己的构造函数来初始化自己的类成员变量,即使另一个类中定义的类成员变量的权限是public,我们也不应该像上述这样去做,因为可能会受到权限的限制,所以最好不要使用这种方法、
拓展:
一、
#define _CRT_SECURE_NO_WARNINGS 1 #includeusing namespace std; class Time { public: Time(int x) //非默认构造函数 { _hour = x; _minute = 0; _second = 0; } int _hour; int _minute; int _second; }; class Date { public: //如果把Time类中的默认构造函数改为非默认构造函数的话,而在Date类中,仍执行Time _time; 的话,那么也是 自动调用 该自定义类型的类成员变量_time的构造函数进行初始化, //但是由于把Time类中的默认构造函数改为非默认构造函数,所以,就会报错说是不存在合适的默认构造函数来使用,故,自动调用不成功,此时若想在Time类体中换为非默认构造函数后的基础上成功运行的话, //就必须在Date类中进行部分修改操作,具体方法在后面的初始化列表中会有讲解,大致方法就如下所示: //初始化列表、 Date() :_time(10) {} void Print() { cout << _year << "->" << _month << "->" << _day << endl; cout << _time._hour << "->" << _time._minute << "->" << _time._second << endl; } private: //内置类型的类成员变量、 int _year; int _month; int _day; //自定义类型的类成员变量、 Time _time; //只是声明,并不是在实例化对象(定义)、 }; int main() { Date d1; d1.Print(); //-858993460-> - 858993460-> - 858993460 //10->0->0 return 0; } 二、
#define _CRT_SECURE_NO_WARNINGS 1 #includeusing namespace std; class Time { public: Time(int x =1) //带参全缺省构造函数、 { _hour = x; _minute = 0; _second =0 ; } int _hour; int _minute; int _second; }; class Date { public: //下面的初始化列表操作不仅对Time的带参构造函数(非全缺省,也非半缺省)起作用,对Time的带参全缺省构造函数起作用,还对Time的带参半缺省构造函数起作用、 //初始化列表、 Date() :_time(10) {} void Print() { cout << _year << "->" << _month << "->" << _day << endl; cout << _time._hour << "->" << _time._minute << "->" << _time._second << endl; } private: //内置类型的类成员变量、 int _year; int _month; int _day; //自定义类型的类成员变量、 Time _time; //只是声明,并不是在实例化对象(定义)、 }; int main() { Date d1; d1.Print(); //-858993460-> - 858993460-> - 858993460 //10->0->0 return 0; }
C++11中有关编译器自动生成的无参的默认构造函数的补丁:
如果某一个类体中不显示的实现构造函数的话,则C++编译器会自动生成一个无参的默认构造函数会,它会对内置类型的类成员变量不进行处理,会对自定义类型的类成员变量进行处理,当该类体中既有自定义类型的类成员变量,还有内置类型的类成员变量时,若使用编译器自动生成的无参的默认构造函数的话,是没有办法对类体中的内置类型的类成员变量进行处理的,但是可以对自定义类型的类成员变量进行处理,那么此时只能手动的在类体中显示的实现构造函数来初始化内置类型的类成员变量,那么,如果不想通过该方法的话,有没有其他的方法能够解决这个问题呢,答案是有的,这就需要C++11中新添加的有关编译器自动生成的无参的默认构造函数的补丁来解决,其工作原理为:在类体中对内置类型的类成员变量声明时进行赋值缺省值,只是给编译器自动生成的无参的默认构造函数进行使用的,此处不是以前所谓的初始化,这是因为,类体中的类成员变量(包括此处所谓的内置类型的类成员变量)均是声明,而不是定义,所以不能叫做初始化,因为初始化是指在定义时进行的赋值操作才能叫做初始化、
#define _CRT_SECURE_NO_WARNINGS 1 #includeusing namespace std; class Time { public: Time() { _hour = 0; _minute = 0; _second = 0; } int _hour; int _minute; int _second; }; class Date { public: void Print() { cout << _year << "->" << _month << "->" << _day << endl; cout << _time._hour << "->" << _time._minute << "->" << _time._second << endl; } private: //C++11中有关编译器自动生成的无参的默认构造函数的补丁、 int _year =2022; int _month =7; int _day =11; Time _time; //此处的类成员变量均是声明,不是定义,均不为他们开辟内存空间、 }; int main() { Date d1; d1.Print(); //2022->7->11 //0->0->0 return 0; }
拓展:
虽然该模块叫做C++11中有关编译器自动生成的无参的默认构造函数的补丁,但是并不是说只有我们不写,编译器自动生成无参的默认构造函数时,此处的在类体中的内置类型的类成员变量的缺省值才起作用,当我们在类体中显示实现构造函数时,该缺省值仍然起作用,但要注意的是,当我们在类体中显示实现的构造函数的函数体中也对该类体中的同一个内置类型的类成员变量进行赋值时,要知道的是,类体中内置类型的类成员变量的缺省值的优先级是最低的,只有当在类体中显示实现的构造函数的函数体内并没有对某一个内置类型的类成员变量赋值时或者是在类体中不显示的实现构造函数时,才会采用类体里面内置类型的类成员变量后面的缺省值,注意,此处所说的缺省值均是指在类体里面的内置类型的类成员变量后面的这个值、
#define _CRT_SECURE_NO_WARNINGS 1 #includeusing namespace std; class Time { public: Time() { _hour = 0; _minute = 0; _second = 0; } int _hour; int _minute; int _second; }; class Date { public: //当在类体中显示的实现构造函数时,在该构造函数的函数体内只对该类体中的内置类型的类成员变量进行操作即可,不需要在函数体内对该类体中的自定义类型的 //类成员变量进行处理,对于自定义类型的类成员变量仍通过自动调用其构造函数(默认和非默认)进行初始化、 1、 //Date(int year=1)//带参全缺省的构造函数(属于默认构造函数)、 //{ // _year = year; //} Date d1; -> 打印结果: 1->7->11 0->0->0 2、 //Date(int year)//带参的构造函数(属于非默认构造函数)、 //{ // _year = year; //} Date d1(2); -> 打印结果: 2->7->11 0->0->0 3、 //Date()//无参的构造函数(属于默认构造函数)、 //{ // _year=3; //} Date d1; -> 打印结果: 3->7->11 0->0->0 void Print() { cout << _year << "->" << _month << "->" << _day << endl; cout << _time._hour << "->" << _time._minute << "->" << _time._second << endl; } private: int _year =2022; int _month =7; int _day =11; Time _time; //此处的类成员变量均是声明,不是定义,均不为他们开辟内存空间、 }; int main() { Date d1; d1.Print(); return 0; }
8、类成员变量的命名风格
类成员变量的命名风格:我们一般习惯在类成员变量的前面加一个_,或者在后面加也可以,或者在类成员变量的前面加一个 m_ 或者在类成员变量前面加一个m,并把类成员变量的变量名的第一个字母大写,其中,m是(member的意思),一般采取第一种方式,这是为了防止下面情况的出现:
#define _CRT_SECURE_NO_WARNINGS 1 #include3、析构函数using namespace std; class Date { public: //代码可以成功编译,因为此处的 year = year; 是指构造函数的形参赋给形参,故没有语法错误,可以成功编译,并不是构造函数的形参赋给某一个对象的类成员变量year,这是因为 //在此编译器要满足就近原则,在执行year = year;时,编译器要去寻找左边的year,要遵循就近原则,即先从构造函数的函数体内去寻找year,此时是可以找到的,故就是所谓的 //构造函数的形参赋给形参,如果在该构造函数体内找不到,才会去类体(类域)中,构造函数外去寻找year,就比如执行_month = month;和_day = day;时,在构造函数的 //函数体中找不到_month和_day,就会去类体(类域)中,构造函数外去寻找,此时就可以找到_month和_day了,但是要注意的是,当执行_month = month;和_day = day;时, //_month和_day并不是指的我们在类体中看到的_month和_day,这是因为,类体中的类成员函数都是声明,不为其开辟内存空间,现在对类成员变量的声明赋值是不可以的,因为他们 //没有实际的内存空间,所以当执行_month = month;和_day = day;时,实际上赋值给的_month和_day而是某个对象中的类成员变量_month和_day,当在构造函数的函数体中找不到_month和_day, //就会去类体(类域)中,构造函数外去寻找,此时就可以找到_month和_day,就会告诉编译器这两者一定是属于某个对象的,所以编译器会进行替换,如下所示: Date(int year, int month, int day) { year = year; _month = month; //this->_month = month; _day = day; //this->_day = day; 而执行year = year;时,是不会进行替换的,因为编译器认为是构造函数的形参赋值给形参,当然,如果我们手动的把year = year; //改成: this->year = year;的话,那么编译器就不再遵循就近原则,而是直接认为是把形参year赋值给某一个对象中的类成员变量year,此时是可以完成初始化的, //但是由于我们在类体中一般不显示的写出this,故当对类成员变量命名的时候要在他们前面加上_ ,这样就不会出现歧义了、 } void Print() { cout << year << "-" << _month << "-" << _day << endl; } private: int year; int _month; int _day; }; int main() { Date d1(2022,7,12); d1.Print(); return 0; }
3.1、概念
前面通过构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没的呢?
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完
成的,而
对象在销毁时会自动调用析构函数,完成类的一些资源清理工作,所谓资源清理是指清理
掉像:动态开辟(堆区),new(堆区),fopen(硬盘)出来的等资源,这些资源需要处理、
问:为什么要有析构函数?为什么析构函数有时候我们还要自己进行定义?
答:因为类的成员变量有时候还会和一些空间产生联系,比如将类的成员变量中的某个成员变量(指针类型)指向了堆区上的一块空间,在这个类的成员变量被销毁后,这块空间依然存在,这就造成了内存泄漏问题,且我们将再也无法找到这块空间,这块空间编译器是无法自己进行回收的,编译器自动生成的默认析构函数也无法回收,只能由程序员自己来进行回收,其次,编译器自动生成的默认析构函数只会对自定义类型的类成员变量进行处理,对内置类型的类成员变量不进行处理,而当内置类型的类成员变量如果有动态开辟/new/fopen出来的资源的话,编译器自动生成的默认构造函数是不会进行处理的,但是这些资源需要被清理掉,所以就需要我们显示的在类体中实现析构函数,其实相当于我们在面向过程中写的Destory函数,构造函数有点类似于Init函数、
3.2、特性
析构函数
是特殊的类成员函数、
其
特征
如下:
1、
析构函数是在类名前加上字符
~
、
2、
无参数无返回值,则析构函数不能构成函数重载,且不写void、
3、
一个类中有且只有一个析构函数,若未在类体中显式定义,则系统会自动生成一个默认析构函 数,且该默认析构函数的函数体为空,由于析构函数没有参数,故析构函数都是默认析构函数、
4、
对象的生命周期结束时,
C++
编译器系统会自动调用析构函数,且只会调用一次、
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
using namespace std;
class Date
{
public:
Date(int year = 0, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
arr = (int*)malloc(sizeof(int)* 10);
assert(arr);
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
~Date()
{
cout << "~Date()函数的调用" << endl;
free(arr);//将堆区上(动态开辟)的那块空间释放回收、
arr = NULL;
}
private:
int _year;
int _month;
int _day;
int* arr;
};
int main()
{
Date d1;
return 0;
}
问:为什么要有析构函数?为什么析构函数有时候我们还要自己进行定义?
答:因为类的成员变量有时候还会和一些空间产生联系,比如将类的成员变量中的某个成员变量(指针类型)指向了堆区上的一块空间,在这个类的成员变量被销毁后,这块空间依然存在,这就造成了内存泄漏问题,且我们将再也无法找到这块空间,这块空间编译器是无法自己进行回收的,编译器自动生成的默认析构函数也无法回收,只能由程序员自己来进行回收,其次,编译器自动生成的默认析构函数只会对自定义类型的类成员变量进行处理,对内置类型的类成员变量不进行处理,而当内置类型的类成员变量如果有动态开辟/new/fopen出来的资源的话,编译器自动生成的默认构造函数是不会进行处理的,但是这些资源需要被清理掉,所以就需要我们显示的在类体中实现析构函数,其实相当于我们在面向过程中写的Destory函数,构造函数有点类似于Init函数、
#define _CRT_SECURE_NO_WARNINGS 1 #include#include using namespace std; class Date { public: Date(int year = 0, int month = 0, int day = 0) { _year = year; _month = month; _day = day; arr = (int*)malloc(sizeof(int)* 10); assert(arr); } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } ~Date() { cout << "~Date()函数的调用" << endl; free(arr);//将堆区上(动态开辟)的那块空间释放回收、 arr = NULL; } private: int _year; int _month; int _day; int* arr; }; int main() { Date d1; return 0; }
5、栈区里定义的对象的构造与析构的顺序:
关于构造的顺序是:先定义的对象则先进行构造,后定义的对象则后进行构造,而关于析构的顺序则是:先定义的对象则后进行析构,后定义的对象则先进行析构,即,析构的顺序和数据结构中的栈保持一致,析构顺序和构造顺序是相反的,例如:
int main()
{
Date d1;
Date d2;
return 0;
}
像上面这样进行对象的定义,则对象d2先析构,对象d1后析构,而对象d1先构造,d2后构造、
嵌套的类的析构顺序也是如此,我们来看下面这个代码:
6、编译器自动生成的默认析构函数,对内置类型的类成员变量将不做处理,但是会对自定类型的类成员变量进行处理,通过自动调用该自定义类型的类成员变量的析构函数进行处理、
#define _CRT_SECURE_NO_WARNINGS 1 #include#include using namespace std; class Time { public: Time() { _hour = 0; _minute = 0; _second = 0; } ~Time() { cout << "~Time()函数的调用" << endl; } //使用编译器自动生成的默认析构函数也是可以的,因为该类中的,类成员变量均是内置类型的,且都不需要处理其资源、 private: int _hour; int _minute; int _second; }; class Date { public: Date(int year = 0, int month = 0, int day = 0) { _year = year; _month = month; _day = day; arr = (int*)malloc(sizeof(int)* 10); assert(arr); } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } ~Date() { cout << "~Date()函数的调用" << endl; free(arr); arr = NULL; } private: //内置类型的类成员变量、 int _year; int _month; int _day;//析构函数的调用顺序只看对象,此处的arr不是对象,所以不考虑其顺序、 int* arr; //自定义类型的类成员变量、 Time _time; //若不在Date类中显示的实现析构函数的话,则编译器会自动生成一个默认的析构函数,其对内置类型的类成员变量不做处理,而对自定义类型的类成员变量进行处理 //但是由于int* arr是动态开辟出来的,所以它的资源需要被清除,而由于它属于内置类型的类成员变量,所以目前没有办法清除它的资源,故只能在Date类中显示的实现 //析构函数用来处理内置类型且是动态内存开辟出来的类成员变量arr,至于内置类型的类成员变量:_year/_month/_day均不需要清除其资源,所以在显示实现的析构函数 //中只对内置类型的类成员变量arr进行处理即可,同理,自定义类型的类成员变量仍是通过 自动调用 其析构函数进行处理,这一点与构造函数是一样的、 }; int main() { Date d1; //~Date()函数的调用 //~Time()函数的调用 return 0; } //Date类中声明了Time类类型的(自定义类型)类成员变量,所以要先调用Date析构函数,然后再调用Time析构函数、


![[C++]C++类和对象(中)---上篇、 [C++]C++类和对象(中)---上篇、](http://www.mshxw.com/aiimages/31/1011973.png)
