类的六个成员函数
空类中真的什么都没有吗?
事实上任何一个类,在我们不写的情况下,都会自动生成6个默认的成员函数。
1.构造函数
概念:
构造函数是一个特殊的成员函数,名字与类名相同,实例化对象时由编译器自动调用,以此来保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。
构造函数的主要作用是初始化对象,而不是开辟空间创建对象。
特征:
1.函数名与类名相同
2.创建对象时,由编译器自动调用,用户无法调用
3.无返回值(void也不可以)
4.构造函数可以重载
5.若类中没有显式定义构造函数,则编译器会自动生成一个无参的默认构造函数;若用户已显式定义,则编译器不再生成
6.无参和全缺省的构造函数都称为默认构造函数,且默认构造函数只能有一个
例1:
例2:
例3:
2.析构函数
概念:
与构造函数相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的,而对象在销毁时,会自动调用析构函数,完成类的一些资源清理工作。
特性:
1.析构函数名是在类名前加上一个字符‘~’。
2.没有参数(可以加void,但一般不会这样使用),没有返回值。
3.一个类有且仅有一个析构函数(不能重载)。若未显式定义,系统则会自动生成默认的析构函数。
4.对象生命周期结束时,C++编译系统会自动调用析构函数。
默认的成员函数,一定会生成?
从语法上来讲,是一定会生成的,但是实际情况中并不一定。因为语法是靠人来具体实现的,有时需要考虑一些其他因素,比如程序的运行效率,所以有可能不会严格按照语法来实现。
比如一下代码,并没有什么意义,在某些编译器及版本中(例:VS2013),就不会生成类的默认成员函数。
3.拷贝构造函数
概念:
拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰,防止被修改),在用已存在的类类型对象创建新对象时,由编译器自动调用。
特征:
1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
3.若未显式定义,系统会生成默认的拷贝构造函数。
注意:默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝属于浅拷贝,或叫值拷贝;所以没有涉及到资源管理,可以直接使用默认的拷贝构造函数。
为什么拷贝构造函数使用传值方式会引发无穷递归?
那我们就假设可以使用传值方式,那么在调用拷贝构造函数时就会发生临时拷贝,需要将实参拷贝到形参中,即通过实参来构造形参对象,那么此时就又需要调用拷贝构造函数,因此陷入无穷递归调用。
什么情况需要自实现拷贝构造函数?
因为默认的拷贝构造函数采用的是浅拷贝,因此当涉及到资源的管理时,就需要自实现拷贝构造函数。
调用拷贝构造函数的场景
(1)用一个对象直接构造一个新对象
(2)类类型对象,以值的形式传参
(3)类类型对象,以值的方式作为函数返回值
注意:若直接创建返回一个匿名对象,编译器不会通过拷贝构造函数创建一个临时对象返回,而是直接将匿名对象返回。
4.赋值运算符重载
概念:
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型、函数名,以及参数列表,其返回值类型与参数列表和普通的函数类似。
函数名:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
注意:
1.不能通过连接其他符号来创建新的操作符,比如operator@
2.重载操作符必须有一个类类型或者枚举类型的操作数
3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
4.作为类成员的重载函数时,其形参看起来比操作数数目少一,其实成员函数的操作符有一个默认的形参this,限定为第一个参数
5.以下五个运算符不能重载:.*、::、sizeof、?:、.
什么情况需要自实现赋值运算符重载?
默认的赋值运算符重载,也是采用的浅拷贝,所以如果类中未涉及到资源管理时,赋值操作是否显示提供都可以,需要则提供,不需要则可不提供,利用编译器完成赋值操作即可。
若类中涉及到资源管理,则用户必须显示实现,否则编译器默认生成的赋值运算符重载实现采用的是浅拷贝,可能造成资源、内存泄漏,和多个对象共享同一份资源,在销毁时造成代码崩溃。
为什么赋值运算符重载必须重载为类的成员函数?
因为赋值运算符重载是类默认的成员函数,若用户没有显示定义时,编译器会自动生成一份,而此时若用户自己再在类外实现一份,相等于存在两个赋值运算符重载形成歧义,编译器则会报错。
较为标准的赋值运算符重载
(1)加返回值,支持连续赋值
类类型对象赋值,相当于是调用赋值运算符重载函数,结合连续赋值的本质,所以需要对赋值运算符重载函数加返回值。
(2)返回引用(前提不影响功能实现),提高效率
返回引用,则返回时不需要再调用拷贝构造函数构造临时对象,提高程序效率。
(3)引用传参,提高效率
(4)const修饰参数,防止参数对象被修改
(5)添加检验,防止本身给本身赋值
实现重载前置++与后置++
因为++运算符是单目运算符,作为类的成员重载函数,自带了默认的形参this,所以为了能够实现前置与后置++重载,在后置++重载函数中多加一个int形参作为区分。
注意:
后置++是先使用,再+1。所以需要先临时保存对象,再对对象数据+1,然后返回的是+1前的临时对象(即这里不可以返回引用)。
实现流提取运算符<<重载
若重载为类的成员函数:
语法上是可以的,但是因为其自带默认参数this,所以其使用逻辑为"对象名<<cout",不符合我们正常的使用逻辑。
<<重载规定:
第一个参数必须是ostream的对象,第二个参数才是要打印的内容,所以一般重载为全局函数。
5.const成员函数
const修饰类的成员函数:
将const修饰的类成员函数称之为const成员函数;const修饰类成员函数,(本质)实际上修饰该成员函数隐藏的this指针,表明在该成员函数中不能对类的任何成员进行修改。
例:
比如以下函数,本意只是想对时间进行一个打印输出,但是在内部可能不小心会对数据造成修改,影响功能实现:
this的类型:Time* const this
即this指针的指向不能修改,但是this指向对象中的内容可以修改。
此时就可以通过const修饰该成员函数,表明在该成员函数中不能对类的任何成员进行修改。
this的类型:const Time* const this
即this指针的指向不能修改,指向对象中的内容也不能修改。
相关问题
(1)const对象能否调用非const成员函数?
不能,const类型对象只能调用const成员函数,不能调用普通成员函数。因为普通成员函数可能会对对象中的成员进行修改。
(2)非const对象能否调用const成员函数?
可以,普通对象对于普通成员函数和const成员函数都可以调用。
(3)const成员函数内,能否调用其他的非const成员函数?
不能,同理非const成员函数可能会对对象的成员进行修改,而const成员函数内不能对对象的任何成员进行修改。
(4)非const成员函数内,能否调用其他的const成员函数?
可以。
注意:
构造函数,拷贝构造函数,析构函数,赋值运算符重载,不可以使用const修饰。因为以上四个函数目的就是对成员进行修改。
mutable关键字
mutable关键字修饰成员变量,表示该成员变量可以在const修饰中被修改。
6.取地址及const取地址操作符重载
取地址操作符重载:
const取地址操作符重载:
注意:
这两个运算符一般使用编译器生成的默认取地址重载成员函数即可,一般不需要自己实现重载。只有特殊情况才需要自己实现,比如想要让别人获取指定的内容等。