####
c++作为面向对象的编程语言,与c语言相比最大的一个特点就是类的使用,使数据与方法打包(变量与函数打包)在类中。而类中很重要的就是默认成员函数,默认成员函数是帮助我们更好处理对象与内存的一种方式,所以我们有必要充分的理解认识它们,这样才能使我们的代码更加流畅便捷易读。
####
所谓默认成员函数,就是在一个空类中,即使我们什么都不写,编译器会自动生成的函数。但是我们也可以自己去写,实现我们想要做到的功能,这时候,编译器就会优先使用我们自己实现的默认成员函数。下面将重点介绍四个重要的默认成员函数。
一、构造函数
1.作用
####构造函数完成的是某一类的一个对象中成员变量的初始化工作。即在对象创建时,编译器就会自动调用构造函数,完成类中成员变量的初始化。
2.函数样式
####构造函数有两种要求,编译器才会将其作为构造函数 (1)函数名必须与类名相同 (2)函数无返回值(无返回值不代表是void,void表示有返回值,返回值为空)
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
3.使用特点
(1)构造函数在创建对象的时候调用,分为两种调用方式,一种是传值调用,一种是默认值调用。 (2)给定值调用就是在创建对象时,我们将想要成员变量初始化的值当作参数传给构造函数,这个要求构造函数形参列表中必须有形参来接受实参。 (3)默认值调用就是指在创建对象的时候,我们不需要传参,在创建对象时直接给定某一默认值来初始化成员变量。这时候构造函数我们称为默认构造函数。 (4)默认构造函数:默认构造函数就是指不用传参就可以调用初始化成员变量的构造函数,一共有三种默认构造函数。 1.编译器自动生成的构造函数:这类构造函数是双标的,对于成员变量中的内置类型,它会不做任何处理,变量中仍然是随机值,而对于自定义类型(class、struct、union),编译器生成的构造函数就会调用该类型的默认构造函数(注意,一定是默认构造函数。因为你没传值,因此该自定义类型的成员变量就会去调用它的类型得到默认构造函数去初始化) 2.无参的构造函数:函数参数列表中没有参数 3.全缺省的默认构造函数:在参数列表中形参全缺省,因此在不传参的情况下就会使用缺省值来初始化成员变量。缺省的构造函数有一个好处,就是即支持传值调用,又支持默认值调用,可以在构造对象时传值来赋予想要的值
Date()
{
_year = 2000;
_month = 1;
_day = 1;
}
Date(int year = 2000, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int main()
{
Date d1;
Date d2(2021,7,22);
}
4.注意事项
(1)最推荐的构造函数类型是全缺省的,这样既可以传参按照自己的意愿初始化,又可以使用默认构造函数。 (2)每个类都必须有默认构造函数,自己不写就会由编译器自己生成一个,但是编译器自动生成的默认构造函数双标:内置类型不处理,自定义类型调用该类型的默认构造函数。因此可能编译器生成的这个不符合自己的需求。 (3)构造函数支持函数重载,这使得可以有多种初始化方式。
二、析构函数
1.作用
####析构函数完成的是在对象声明周期接受后自动调用进行资源的清理(开辟空间的释放等)不是完成对象的销毁。对象的销毁是在函数栈帧销毁时自动完成的,根本不需要认为处理。
2.函数样式
####析构函数要满足下面两个要求,编译器才会将它当作析构函数 (1)函数名为类名前加~ (2)函数无参,无返回值。
~Date()
{
}
3.使用特点
(1)析构函数在对象生命周期结束后自动调用 (2)析构函数有且仅有一个,如果为显示定义,那么作为类的默认成员函数,编译器会自动生成一个析构函数,但是这个析构函数也是双标的:对于内置类型,不会进行任何处理,对于自定义类型,则会调用它的析构函数。 (3)析构函数只有一个,无法函数重载。 (4)对于有些类,像成员变量只有int类型的日期类,析构函数就是什么用也没有,因此我们可以不用写,用编译器自己生成的就够了。但是对于动态开辟的内存的成员变量,那么我们必须自己写来释放内存,因为编译器自己生成的不会有释放空间这个功能。
4.注意事项
####因为对象都是在函数栈帧上开辟空间的,所以析构函数的顺序应该符合栈的后入先出,即:后创建的对象先析构。
三、拷贝构造函数
1.作用
####用已存在的对象来创建新对象,并且使新对象的值与已存在对象的值相同,类似于拷贝,因此称为拷贝构造。
2.函数样式
####满足以下条件,编译器才将其处理为拷贝构造函数。 (1)函数名与类名相同(与构造函数形成函数重载) (2)函数参数只有一个,为同类型对象并且引用传参.传值传参会引发无穷递归。因为:在传值的时候,就需要用实参拷贝构造出形参,目的是实现拷贝构造,结果你在实现的过程中使用了拷贝构造,就会无穷递归
Date(const Date& d)
{
_year = d.-year;
_month = d._month;
_day = d._day;
}
3.使用特点
(1)拷贝构造在创建新对象的时候自动调用 (2)在使用编译器自动生成的时候,对于内置类型会进行浅拷贝,对于自定义类型会调用该类型自己的拷贝构造函数。 (3)使用方式:
int main()
{
Date d1(2021,7,21);
Date d2 = d1;
Date d2(d1)
return 0;
}
4.注意事项
####对于编译器默认生成的拷贝构造函数来说,完成的是浅拷贝。对于深浅拷贝问题,是一个比较大的问题,在这里我们暂且先知道,浅拷贝,就是无脑的将值毫无改变的赋值过去,然后就会出现一个问题,就是如果成员变量中有指针,那么指针存储的地址也是完全一样的,指向同一块空间,必然会出现一系列的问题,这个时候我们就需要自己写拷贝构造函数,来完成深拷贝。
四、复制运算符重载
1.作用
####这个函数的功能你可以认为就是对象之间的赋值,就和赋值运算符“ = ”对于内置类型的作用一样。
2.函数样式
####符合运算符重载的要求,同时为了支持连续赋值,所以返回值必须是该类型的引用。参数也是该类型的应用,并且因为不会改变内容,最好加const修饰
Date& operator=(const Data& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
3.使用特点
(1)当需要将一个对象的值给另一个对象的时候可以使用。 (2)自己不写,在使用编译器会自动生成,对于内置类型会进行浅拷贝,对于自定义类型会调用该类型自己的 赋值运算符重载函数。 (3)使用方式
int main()
{
Date d1(2021,5,6);
Date d2(2024,6,9);
d2 = d1;
}
4.注意事项
(1)赋值重载与拷贝构造的机理完全类似,都会存在浅拷贝存在的问题,因此必要时,还是需要自己写赋值重载。 (2)不能通过连接其他符号来创建新的操作符:比如operator@。 (3)重载操作符必须有一个类类型或者枚举类型的操作数。 (4)用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义。 (5)作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的 操作符有一个默认的形参this,限定为第一个形参。 (6).* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
总结
####其实还有两个默认成员函数没提到取地址及const取地址操作符重载但是这两个使用编译器默认生成的就足够了,完全没必要自己写,而且也十分简单。 ####总结一下:默认成员函数都是自己不写编译器可以自己生成的,但是编译器生成的很难达到我们想要的那种效果。如:内置类型构造函数的初始化、浅拷贝问题因此我们要根据实际情况来实现默认成员函数。来为代码保驾护航。 #### #### #### ####如有错误,请指正。谢谢! ####有问题请在评论区留言,我会尽力解答!
|