【C++语法】类和对象(3)
5.类和对象(3)
文章目录
- 5.类和对象(3)
- 回顾
- 析构函数
- 特点
- 使用析构函数时应注意
- 拷贝构造函数
- 特点
- 补充:为什么拷贝构造函数的参数只能是引用
- 在实践中的总结
- 再次补充函数调用的步骤——返回值为自定义类型的变量
- 赋值重载
- (1)运算符重载
- 特点
- 补充:运算符重载的细节
- (2)赋值重载
回顾
前面已经讲了构造函数,忘记了可以跳转过去看一看:
【C++语法】类和对象(2)
【C++语法】类和对象(1)
析构函数
析构函数与构造函数的功能差不多相反,当实例化的对象被销毁时,会自动调用析构函数,主要的作用是资源的清理(如动态开辟内存空间的释放等)。特别强调:析构函数不是用于被实例化的对象的销毁,这部分工作由编译器完成,因为大部分对象的类型为动态局部对象。
特点
-
函数名称:~[类名] 就是比构造函数前面多了一个~
-
无参数,无返回值,不写void;
-
析构函数只能有一个(因为他不能构成函数重载),如果没有显式定义,编译器会生成默认的析构函数。
-
析构函数可以被显式调用,也就是说可以由用户调用
-
析构函数在对象生命周期结束时自动调用。
使用析构函数时应注意
-
需要资源清理时才使用析构函数;
-
编译器自动生成的析构函数与构造函数类似:对内置类型不做处理,对自定义类型调用其析构函数。
拷贝构造函数
拷贝构造函数是构造函数的中的一种特殊情况,它主要的作用是通过其他相同类定义的对象构造(初始化)另一个对象,使这两个对象一模一样
特点
-
拷贝构造函数是构造函数的一种重载形式,一种特殊的构造函数
-
拷贝构造有且只有一个参数,并且这个参数是该类类型的引用;注意一定是别名,不能是传值(如果是传值的话,会引发无穷递归的情况),也不能是指针(C++语法规定,不然不叫拷贝构造函数,只能算是一个普通的构造函数,在使用时也会有区别这些后面再补充
-
如果没有显式定义拷贝构造函数,编译器会自动生成默认拷贝构造函数,这个拷贝构造函数可以实现对象的值拷贝(浅拷贝),也就是按照字节序在内存中的存储方式,直接复制。因为是浅拷贝,所以在拷贝含有指针等变量的时候会出现一些问题
对上面的代码进行分析:
-
因为拷贝构造是构造的一种重载形式,因此对原构造函数没有影响,d1调用原构造函数,d2、d3调用拷贝构造函数
-
拷贝构造函数使用时可以有d2和d3两种形式,结果都是一样的
-
d2:private中的缺省值—>拷贝构造函数
补充:为什么拷贝构造函数的参数只能是引用
-
不能传值:
这里我们要学习另一个知识:在函数调用时如果函数参数是内置类型,那么就会直接拷贝,但如果是自定义类型,就会先调用该内置类型的拷贝构造函数,调用拷贝构造函数结束之后再调用该函数。如果是传参调用,就会发生无限调用拷贝构造函数的情况,而且编译器不会通过如下面的代码所示:在函数调用的过程中,如果函数的形参表是一个类class的变量(不是引用),那么在调用函数之前会先根据传过来的实参调用拷贝构造生成形参变量,再调用该函数
如果是传参调用,就会发生无限调用拷贝构造函数的情况:
-
不能用指针
因为规定,如果用指针,编译器不认为这是拷贝构造函数,会把他当成一个普通的构造函数对待,也就是说用d2,d3的方法不会调用这个“含指针”的构造函数,而编译器会自动生成一个默认的拷贝构造函数(这里要重点注意)
-
编译器生成的默认拷贝构造函数仅仅是值拷贝,不能完成深拷贝
在实践中的总结
1、如果没有管理资源,一般情况不需要写拷贝构造,默认生成的拷贝构造就可以。如:Date(日期类)
2、如果都是自定义类型成员,内置类型成员没有指向资源(如指针),也类似默认生成的拷贝构造就可以。
3、一般情况下,不需要显示写析构函数,就不需要写拷贝构造
4、如果内部有指针或者一些值指向资源,需要显示写析构释放,通常就需要显示写构造完成深拷贝
再次补充函数调用的步骤——返回值为自定义类型的变量
前面讲的是函数传值调用的参数如果是自定义类型会先调用它的拷贝构造函数再调用此函数,这里如果函数调用结束之后如果要返回一个自定义类型的变量,不会直接返回这个变量的值,首先会再创建一个临时变量,将这个变量的值用拷贝构造给这个临时变量,再由这个临时变量返回它的值
我们可以直接看下面函数调用的关系:
总结一下:
-
返回对象是一个局部对象或者临时对象,出了当前func函数作用域,就析构销毁了,那么不能用引用返回。用引用返回是存在风险的,因为引用对象在func函数栈帧已经销毁了。虽然引用返回可以减少一次拷贝,但是出了函数作用,返回对象还在,才能用引用返回
-
不能说只要是在栈帧上创建的对象都不能返回,因为this指针也是在栈上创建的,但类的成员函数的返回值可以是this指针或者它的解引用,因为this指针的栈帧不是在这个成员函数上,而是在创建这个类类型的对象的函数上
例如:假如你是在mian函数上创建的这个类类型的对象(变量),那么在main函数销毁之前,这个类的this指针是可以作为其他成员函数的返回值的,原因上面讲的很清楚
赋值重载
(1)运算符重载
在学习赋值重载之前,我们先了解一下运算符重载,C++中我们可以通过运算符重载的方式来定义函数,使程序的可读性增强。以运算符重载的方式定义函数需要用到一个关键字:operator
函数定义的基本格式主要是函数名的区别,其他与一般的函数定义相同,函数名:operator【运算符】
特点
-
不能使用原来没有意义的字符作为运算符进行函数重载,如@
-
重载操作数(也就是函数参数)必须至少有一个是自定义类型,因为C++中的内置类型关于运算符的运算都已经给了,不能修改
-
最好不要改变其含义(不要与原来运算符的功能相差太大),但程序不会报错(这一点只是一个建议)
-
.*:?:·sizeof
这五个运算符不支持运算符重载 -
运算符重载后的函数主要有两种调用方式:
1>像一般函数一样直接调用:Operator[运算符](函数参数1,函数参数2)
2>像运算符一样写成表达式:[函数参数1][重载运算符][函数参数2]
注意函数参数1和函数参数2的位置不能互换,有规定
如下边所示代码,我们是在类之外定义的运算符重载,但通常情况下类中的数据不能再类之外调用(用private保护的),我们通常会在类中定义运算符重载
此时函数的定义和使用有一些不一样:
另一种定义方式:
补充:运算符重载的细节
-
运算符重载为类的成员函数时,在使用这个重载的运算符时,第一个参数总是隐含的this指针作为参数,之后的形参按照顺序排列
如下边代码所示:我们在类中重载了+的运算,但原来+对于C++内置类型的计算不变。其中,当我们在使用时用两种方法:
1>a1.operator+(a2)
2>a1+a2
这两种方法的执行效果相同,都是a1根据重载运算加上a2
在使用时我们要注意:-
a1,a2的位置不能改变,要一一对应
-
重载函数中
(x+a._x)*(y+a._y)
相当于(this->_x + a._x)*(this->_y + a._y)
-
-
赋值运算符只能重载成类的成员函数,不能重载成全局函数;
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自已实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。 -
当用户在创建类的时候,如果没有显示重载赋值运算,编译器会默认自动生成一个赋值运算符重载,以值的方式逐字节拷贝。内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。相当于不定义拷贝构造函数的浅拷贝情况如果像日期类这样的简单类可以不用自己定义赋值运算符重载,但如果需要进行资源管理包括动态内存的申请或指针等,就需要自己定义赋值运算符重载
(2)赋值重载
特点:
-
赋值重载有返回值,返回值是表达式左边的变量,因为要兼容连续赋值的功能
-
自定义类型的函数参数最好用引用,要更加高效,记得加const修饰
-
注意区分构造函数的使用与赋值重载的使用的区别
-
不要自己给自己赋值了