1. 重写(覆盖):虚函数
在很多C++书籍上没有覆盖(overrride)这种称谓,而是直接称为多态(polymorphism)
#include <iostream>
using namespace std;
class Base{
public:
Base(){
cout << __func__ << endl;
}
~Base(){
cout << __func__ << endl;
}
virtual void Test(){ // 虚函数
cout << "Base::Test" << endl;
}
};
class Derive:public Base{
public:
Derive(){
cout << __func__ << endl;
}
~Derive(){
cout << __func__ << endl;
}
void Test(){
cout << "Derive::Test" << endl;
}
};
int main(){
Derive d;
Base& b = d;
b.Test(); // 对象类型决定调用函数
}
结果为:
Base
Derive
Derive::Test
~Derive
~Base
多态中虚函数的作用:
- 对象类型决定调用顺序
- 如果调用的函数是虚函数(加了virtual),查看指针/引用指向对象类型中,是否有完全相同的函数,如果有,调用指向对象中的函数,否则调用父类函数
重写的应用:直角三角形计算面积方法可以不用海伦公式,而是用两直角边的乘积除以2
#include <iostream>
#include <cmath>
using namespace std;
// 三角形
class Triangle{
protected:
int a,b,c;
public:
Triangle():a(0),b(0),c(0){}
Triangle(int a,int b,int c):a(a),b(b),c(c){}
int GetLength() const{
return a+b+c;
}
virtual float GetArea() const{ // 虚函数,在前面加virtual
float q = GetLength()/2.0;
return sqrt(q*(q-a)*(q-b)*(q-c)); //海伦公式
}
};
// 等腰三角形
class IsoscelesTriangle:public virtual Triangle{ /* 虚继承 */
public:
IsoscelesTriangle(int side,int iso){
a = side;
b = iso;
c = iso;
}
};
// 等边三角形
class EqualTriangle:public Triangle{
public:
EqualTriangle(int a):Triangle(a,a,a){}
};
// 直角三角形
class RightAngledTriangle:public virtual Triangle{ /* 虚继承 */
public:
RightAngledTriangle(int a,int b,int c):Triangle(a,b,c){}
float GetArea() const{
cout << "RightAngledTriangle::GetArea" << endl; // 表明调用到的是这个函数
return a*b/2.0;
}
};
// 等腰直角三角形
class IsoselesRightAngledTriangle:public IsoscelesTriangle,public RightAngledTriangle{
public:
IsoselesRightAngledTriangle(int side,int iso):IsoscelesTriangle(side,iso),RightAngledTriangle(side,iso,iso){}
};
void Print(Triangle t){
cout << t.GetLength() << " " << t.GetArea() << endl;
}
void Print2(Triangle& t){
cout << t.GetLength() << " " << t.GetArea() << endl;
}
void Print3(Triangle* t){
cout << t->GetLength() << " " << t->GetArea() << endl;
}
int main(){
Triangle t(3,4,5);
Print2(t);
RightAngledTriangle r(3,4,5);
Print2(r); // Triangle& t = r;
Triangle* arr[] = {
new Triangle(2,3,4),
new IsoscelesTriangle(3,5),
new EqualTriangle(5),
new IsoselesRightAngledTriangle(sqrt(2),1) // 调用的是直角三角形
};
for(int i=0;i<4;++i){
cout << arr[i]->GetLength() << "\t" << arr[i]->GetArea() << endl;
}
}
结果为:
12 6
12 RightAngledTriangle::GetArea
6
9 2.90474
13 7.15454
15 10.8253
3 RightAngledTriangle::GetArea
0.5
对于上述的这个例子:
- 如果是普通三角形,直接调用自己的成员函数
- 如果是直角三角形,对象类型决定调用顺序,所以本应该调用父类下的面积函数,但由于父类下的面积函数是虚函数,所以查看指针/引用指向对象类型中,有完全相同的函数,调用指向对象中的函数
- 如果是特殊的非直角三角形,对象类型决定调用顺序,所以本应该调用父类下的面积函数,但由于父类下的面积函数是虚函数,所以查看指针/引用指向对象类型中,没有完全相同的函数,依然调用父类函数
覆盖成立的三个条件:
- 继承
- 子类覆盖(重写)父类虚函数
- 父类指针/引用指向子类
虚函数定义规则:
- 如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,有无const,那么即使加上了virtual关键字,也是不会覆盖。所以需要两个函数完全相同。
- 只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数。
- 静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。
- 内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。即使虚函数在类的内部定义,但是在编译的时候系统仍然将它看做是非内联的。
- 构造函数不能是虚函数,因为构造的时候,对象还是一片未定型的空间,只有构造完成后,对象才是具体类的实例。
- 析构函数可以是虚函数,而且通常声明为虚函数。
2. 虚析构函数
#include <iostream>
using namespace std;
class Base{
public:
Base(){
cout << __func__ << endl;
}
virtual ~Base(){ // 虚析构函数,前面加virtual,先去调用子类的析构函数再调用父类的析构函数
cout << __func__ << endl;
}
virtual void Test(){
cout << "Base::Test" << endl;
}
};
class Derive:public Base{
public:
Derive(){ // Derive():Base(){}
cout << __func__ << endl;
}
~Derive(){
cout << __func__ << endl;
}
void Test(){ // 同名隐藏
cout << "Derive::Test" << endl;
}
};
int main(){
// Derive d;
// Base* p = &d;
Base* p = new Derive;
p->Test();
delete p; // 因为p是base类型的,所以只调用了父类的析构函数,不调用子类的析构函数
}
结果为:
Base
Derive
Derive::Test
~Derive
~Base
定义虚析构函数表示:
表示先去调用子类的析构函数再调用父类的析构函数
如果类中定义了虚函数,一定要定义虚析构函数
避免内存泄漏的发生
3. 虚函数的本质
#include <iostream>
using namespace std;
class Base{
// void* vtpr; 虚函数表指针,虚函数表是一个virtual函数指针数组
int n;
public:
Base(){
cout << __func__ << endl;
}
~Base(){
cout << __func__ << endl;
}
virtual void Test(){
cout << "Base::Test" << endl;
}
void Test(int n){
cout << "Base::Test(" << n << ")" << endl;
}
};
int main(){
Base b;
cout << sizeof(b) << endl;
}
结果为:
Base
16
~Base
4. 纯虚函数
override 用来检查子类的这个函数有没有对应父类中可以覆盖的函数
如果找不到或者写错,就会报错
final 写在父类成员函数后,终止后续继承的函数进行重写
如果对应子类中有重写的成员函数,就会报错
#include <iostream>
using namespace std;
class Base{
public:
virtual void Test(){
cout << "Base::Test" << endl;
}
};
class Derive:public Base{
public:
void Test() override {
cout << "Derive::Test" << endl;
}
};
int main(){
Base b;
b.Test();
Derive d;
d.Test();
// 多态/覆盖
Base& f = d;
f.Test();
}
结果为:
Base::Test
Derive::Test
Derive::Test
纯虚函数:
#include <iostream>
using namespace std;
class Base{ // 抽象类
public:
virtual void Test() = 0; // 纯虚函数
/* 是不实现的
{
cout << "Base::Test" << endl;
}
*/
};
class Derive:public Base{
public:
void Test() override {
cout << "Derive::Test" << endl;
}
};
int main(){
// Base b; 抽象类不能定义对象
// b.Test();
Derive d;
// 多态/覆盖
Base& f = d; // 可以定义指针和引用
f.Test();
}
结果为:
Derive::Test
纯虚函数的应用:
定义抽象类,可以计算圆形和三角形的周长和面积,计算公式不同可因情况而分析,更加灵活
#include <iostream>
#include <cmath>
using namespace std;
class Shape{ // 抽象类
public:
virtual int GetLength() const = 0;
virtual float GetArea() const = 0;
};
// 圆形
class Circle:public Shape{
int r;
public:
Circle(int r):r(r){}
int GetLength() const{
return 2*M_PI*r;
}
float GetArea() const{
return M_PI*r*r;
}
};
// 三角形
class Triangle:public Shape{
protected:
int a,b,c;
public:
Triangle():a(0),b(0),c(0){}
Triangle(int a,int b,int c):a(a),b(b),c(c){}
int GetLength() const{
return a+b+c;
}
float GetArea() const /* final */{
float q = GetLength()/2.0;
return sqrt(q*(q-a)*(q-b)*(q-c));
}
};
// 等腰三角形
class IsoscelesTriangle:public virtual Triangle{ /* 虚继承 */
public:
IsoscelesTriangle(int side,int iso){
a = side;
b = iso;
c = iso;
}
};
// 等边三角形
class EqualTriangle:public Triangle{
public:
EqualTriangle(int a):Triangle(a,a,a){}
};
// 直角三角形
class RightAngledTriangle:public virtual Triangle{ /* 虚继承 */
public:
RightAngledTriangle(int a,int b,int c):Triangle(a,b,c){}
};
// 等腰直角三角形
class IsoselesRightAngledTriangle:public IsoscelesTriangle,public RightAngledTriangle{
public:
IsoselesRightAngledTriangle(int side,int iso):IsoscelesTriangle(side,iso),RightAngledTriangle(side,iso,iso){}
};
int main(){
Shape* arr[] = {
new Triangle(2,3,4),
new IsoscelesTriangle(3,5),
new EqualTriangle(5),
new IsoselesRightAngledTriangle(sqrt(2),1),
new Circle(1) // 求圆形
};
for(int i=0;i<5;++i){
cout << arr[i]->GetLength() << "\t" << arr[i]->GetArea() << endl;
}
结果为:
9 2.90474
13 7.15454
15 10.8253
3 0.433013
6 3.14159
重载和覆盖的区别:
重载 | 覆盖 |
---|---|
重载要求函数名相同,但是参数列表必须不同,返回值可以相同也可以不同。 | 覆盖要求函数名、参数列表、返回值必须相同。 |
在类中重载是同一个类中不同成员函数之间的关系。 | 在类中覆盖则是子类和基类之间不同成员函数之间的关系。 |
重载函数的调用是根据参数列表决定。 | 覆盖函数的调用是根据对象类型决定。 |
重载函数是在编译时确定调用一个函数。 | 覆盖函数是在执行时确定调用个函数。 |