C++基础:多态,虚函数的用法

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

多态中虚函数的作用:

  1. 对象类型决定调用顺序
  2. 如果调用的函数是虚函数(加了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

对于上述的这个例子:

  1. 如果是普通三角形,直接调用自己的成员函数
  2. 如果是直角三角形,对象类型决定调用顺序,所以本应该调用父类下的面积函数,但由于父类下的面积函数是虚函数,所以查看指针/引用指向对象类型中,有完全相同的函数,调用指向对象中的函数
  3. 如果是特殊的非直角三角形,对象类型决定调用顺序,所以本应该调用父类下的面积函数,但由于父类下的面积函数是虚函数,所以查看指针/引用指向对象类型中,没有完全相同的函数,依然调用父类函数

覆盖成立的三个条件:

  1. 继承
  2. 子类覆盖(重写)父类虚函数
  3. 父类指针/引用指向子类

虚函数定义规则:

  1. 如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,有无const,那么即使加上了virtual关键字,也是不会覆盖。所以需要两个函数完全相同
  2. 只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数
  3. 静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。
  4. 内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。即使虚函数在类的内部定义,但是在编译的时候系统仍然将它看做是非内联的。
  5. 构造函数不能是虚函数,因为构造的时候,对象还是一片未定型的空间,只有构造完成后,对象才是具体类的实例。
  6. 析构函数可以是虚函数,而且通常声明为虚函数。

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

重载和覆盖的区别:

重载覆盖
重载要求函数名相同,但是参数列表必须不同,返回值可以相同也可以不同。覆盖要求函数名、参数列表、返回值必须相同。
在类中重载是同一个类中不同成员函数之间的关系。在类中覆盖则是子类和基类之间不同成员函数之间的关系。
重载函数的调用是根据参数列表决定。覆盖函数的调用是根据对象类型决定。
重载函数是在编译时确定调用一个函数。覆盖函数是在执行时确定调用个函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值