多态和虚函数

多态与虚函数

在类的定义中,前面有virtual定义的即为虚函数
virtual关键字只用在定义类的函数声明时,写具体函数时不用。

class Base{
private:

public:
    virtual int get(){}
};

int base::get() {

}

对于派生类的对象可以赋值给基类,或者基类可以引用派生类。
如果基类指针或者引用指向基类对象,那么调用基类虚函数
如果基类指针或者引用指向派生对象,那么调用派生类对象

class A{
private:
public:
    virtual void Print() { cout << "A::print" << endl; }
};

class B:public A{
private:
public:
    virtual void Print() { cout << "B::print" << endl; }
};

class D :public A {
private:
public:
    virtual void Print() { cout << "D::print" << endl; }
};

class E :public B {
private:
public:
    virtual void Print() { cout << "E::print" << endl; }
};

int main() {
    A a; B b; E e; D d;
    A * pa = &a; B * pb = &b;
    D * pd = &d; E * pe = &e;
    pa->Print(); // a.Print()被调用,输出:A::Print
    pa = pb;
    pa->Print(); //b.Print()被调用,输出:B::Print
    pa = pd;
    pa->Print(); //d. Print ()被调用,输出:D::Print
    pa = pe;
    pa->Print(); //e.Print () 被调用,输出:E::Print
    return 0;
}

这里写图片描述

游戏编程实例

这里写图片描述

  • 非多态的实现方法
class CCreature{
private:
    int nPower; int nLifeValue;
public:
};

class CDragon:public CCreature{
private:
public:
    void Attack(CWolf *pWolf) {
        //表现攻击动作代码
        pWolf->Hurted(nPower);
        pWolf->FightBack(this);
    }

    void Attack(CGhost *pGhost) {
        //表现攻击动作代码
        pWolf->Hurted(nPower);
        pWolf->FightBack(this);
    }

    void Hurted(int nPower) {
        //表现受伤动作代码
        nLifeValue -= nPower;
    }

    void FightBack(CWolf *pWolf) {
        pWolf->Hurted(nPower / 2);
    }

    void FightBack(CGhost *pGhost) {
        pGhost->Hurted(nPower / 2);
    }
};

此时,n种生物,CDragon中会有n个Attack函数…..,其他类也如此。

  • 多态的实现方法
class CCreature{
private:
    int m_nPower; int m_nLifeValue;
public:
    virtual void Attack(CCreature *pCreature);
    virtual void Hurted(int nPower);
    virtual void FightBack(CCreature *pCreature);
};

class CDragon:public CCreature{
private:
public:
    virtual void Attack(CCreature *pCreature);
    virtual void Hurted(int nPower);
    virtual void FightBack(CCreature *pCreature);
};

void CDragon::Attack(CCreature *pCreature) {
    //表现攻击动作代码
    pCreature->FightBack(this);
    pCreature->Hurted(m_nPower);
}

void CDragon::Hurted(int nPower) {
    //表现受伤代码
    m_nLifeValue -= nPower;
}

void CDragon::FightBack(CCreature *pCreature) {
    //表现反击动作代码
    pCreature->Hurted(m_nPower / 2);
}

更多实例

  • 几何形体处理程序

输入: n 个数目,即n行,每行以c开头
c:R-矩形-后面接长和宽
c:C-圆形-后面接半径
c:T-三角形-后面接三条边
输出:
从小到大输出几何形体的种类和面积

class CShape
{
private:
public:
    virtual double Area() = 0;//纯虚函数
    virtual void PrintInfo() = 0;//纯虚函数
};

class CRectangle:public CShape{
private:
public:
    int w, h;
    virtual double Area();
    virtual void PrintInfo();
};

class CCircle:public CShape{
private:
public:
    int r;
    virtual double Area();
    virtual void PrintInfo();
};

class CTriangle:public CShape{
private:
public:
    int a, b, c;
    virtual double Area();
    virtual void PrintInfo();
};

double CRectangle::Area() {
    return w*h;
}

void CRectangle::PrintInfo() {
    cout << "Rectangle:" << Area() << endl;
}

double CCircle::Area() {
    return 3.14*r*r;
}

void CCircle::PrintInfo() {
    cout << "Circle:" << Area() << endl;
}

double CTriangle::Area() {
    double p = (a + b + c) / 2;
    return sqrt(p*(p-a)*(p-b)*(p-c));
}

void CTriangle::PrintInfo() {
    cout << "Triangle:" << Area() << endl;
}

CShape *pShapes[100];
int MyCompare(const void *s1, const void *s2);

int main() {
    int i, n;
    CRectangle *pr; CTriangle *pt; CCircle *pc;

    cin >> n;
    for (int i = 0; i <n;i++) {
        char c;
        cin >> c;
        switch (c){
        case 'R':
            pr = new CRectangle();
            cin >> pr->w >> pr->h; 
            pShapes[i] = pr;
            break;
        case 'C':
            pc = new CCircle();
            cin >> pc->r;
            pShapes[i] = pc;
            break;
        case 'T':
            pt = new CTriangle();
            cin >> pt->a >> pt->b >> pt->c;
            pShapes[i] = pt;
            break;
        }
    }

    qsort(pShapes, n, sizeof(CShape*), MyCompare);
    for (int i = 0; i < n;i++) {
        pShapes[i]->PrintInfo();
    }
    return 0;
}

int MyCompare(const void*s1,const void*s2){
  double a1,a2;
  CShape **p1;//s1,s2是 void*,不可写“*s1”来取得s1指向的内容
  CShape **p2;
  //s1,s2是指向 pShapes 数组中的元素,元素类型是CShape*,即 s1,s2是指向指针的指针
  p1 = (CShape**)s1;
  p2 = (CShape**)s2;
  //那么想取得他们所指向的内容只能.*p1
  a1 = (*p1)->Area();
  a2 = (*p2)->Area();
  if (a1<a2) {
      return -1;
  }else if (a2<a1){
      return 1;
  }else{
      return 0;
  }
}

这里写图片描述

这里写图片描述
- 2

class Base {
public:
    void fun1() { fun2(); }
    virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived :public Base {
public:
    virtual void fun2() { cout << "Derived:fun2()" << endl; }
};
int main() {
    Derived d;
    Base * pBase = &d;
    pBase->fun1();
    return 0;
}

结果

这里写图片描述

可以见下一段代码:

class Base {
public:
    void fun1() { fun2(); }
    virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived :public Base {
public:
    virtual void fun2() { cout << "Derived:fun2()" << endl; }
};
int main() {
    Base d;
    Base * pBase = &d;
    pBase->fun1();
    return 0;
}

这里写图片描述

再见一段代码

class Base {
public:
    void fun1() { this->fun2(); } //this是基类指针,fun2是虚函数,所以是多态
    virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived :public Base {
public:
    virtual void fun2() { cout << "Derived:fun2()" << endl; }
};
int main() {
    Derived d;
    Base * pBase = &d;
    pBase->fun1();
    return 0;
}

这里写图片描述

总结:在构造函数和析构函数中调用虚函数,不是多态。编译时即可确定,调用的函数是自己的类或基类中定义的函数,不会等到运行时候才决定调用自己的还是派生的。


小quiz

class myclass {
public:
    virtual void hello() { cout << "hello from myclass" << endl; };
    virtual void bye() { cout << "bye from myclass" << endl; }
};
class son :public myclass {
public:
    void hello() { cout << "hello from son" << endl; };
    son() { hello(); };
    ~son() { bye(); };
};
class grandson :public son {
public:
    void hello() { cout << "hello from grandson" << endl; };
    void bye() { cout << "bye from grandson" << endl; }
    grandson() { cout << "constructing grandson" << endl; };
    ~grandson() { cout << "destructing grandson" << endl; };
};

int main() {
    grandson gson;
    son *pson;
    pson = &gson;
    pson->hello(); //多态
    return 0;
}

这里写图片描述

解释:从一开始定义grandson类型 gson,就要从顶端开始执行构造函数。好,myclass调用默认构造函数,对于son,则调用hello()函数,注意,此时不是多态,因为是在构造函数里面,所以生成 “hello from son”,接着调用grandson的构造函数,生成“constructing grandson”;接着定义son指针,并且让pson指向gson,所以到pson->hello()的时候,打印出”hello from grandson”,此时是多态哦。最终调用各自析构函数,由底到顶。

多态实现原理

class Base {
public:
    int i;
    virtual void Print() { cout << "Base:Print"; }
};
class Derived : public Base {
public:
    int n;
    virtual void Print() { cout << "Drived:Print" << endl; }
};
int main() {
    Derived d;
    cout << sizeof(Base) << "," << sizeof(Derived) << endl; ;
    return 0;
}

这里写图片描述

这里写一下自己的理解,base里面含有int类型,大小是4个字节,因为每一个有虚函数的类(或者由虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都存放着虚函数表的指针,所以多出来的4个字节就是存放虚函数表的地址的。
这里写图片描述


一开始我在想,为什么函数不占字节呢,其实,如果在基类中加入函数也是不占据字节的,不信,请看:

class Base {
public:
    int i;
    void print2() { cout << "Base:Print-1"; }
    virtual void Print() { cout << "Base:Print"; }
};
class Derived : public Base {
public:
    int n;
    virtual void Print() { cout << "Drived:Print" << endl; }
};
int main() {
    Derived d;
    cout << sizeof(Base) << "," << sizeof(Derived) << endl; ;
    return 0;
}

这里写图片描述

因此,对于多态的原理,用以下两张图片即可解释:

这里写图片描述

这里写图片描述

即:多态的函数调用语句被编译成一系列根据基类指针所指向的(或者基类引用的)对象中存放的虚函数的地址,在虚函数表中查找虚函数地址,并调用相关指令。

虚析构函数

class CSon {
public: ~CSon() { };
};
class CGrandson :public CSon {
public: ~CGrandson() { };
};
int main() {
    CSon *p = new CGrandson();
    delete p;
    return 0;
}

通过基类的指针删除派生类对象,只会调用基类的析构函数,这样会导致内存未被完全清除


因此,我们希望能够先调用派生类的析构函数,再调用基类析构函数(不可以将虚函数作为构造函数)

  • 1
class son{
public:
~son() { cout<<"bye from son"<<endl; };
};
class grandson : public son{
public:
~grandson(){ cout<<"bye from grandson"<<endl; };
};
int main(){
son *pson;
pson=new grandson;
delete pson;
return 0;
}

这里写图片描述

  • 2
class son {
public:
    virtual ~son() { cout << "bye from son" << endl; };
};
class grandson : public son {
public:
    ~grandson() { cout << "bye from grandson" << endl; };
};
int main() {
    son *pson;
    pson = new grandson;
    delete pson;
    return 0;
}

这里写图片描述

纯虚函数和抽象类

  • 纯虚函数

纯虚函数:没有函数体的虚函数

class A {
private:
    int a;
public:
    virtual void Print() = 0; //纯虚函数 
    void fun() { cout << “fun”; }
};
  • 抽象类

定义:包含纯虚函数的类
1、只能作为基类来派生新类使用
2、不能创建抽象类的对象
3、抽象类可以进行指针和引用–>由抽象类派生出来的对象

A a; // 错, A 是抽象类, 不能创建对象
A * pa; // ok, 可以定义抽象类的指针和引用
pa = new A; //错误, A 是抽象类, 不能创建对象
  • 纯虚函数和抽象类

抽象类中

  1. 成员函数可以调用纯虚函数
  2. 构造/析构函数不能调用纯虚函数

如果一个类从抽象类派生而来,只有当他实现基类中所有纯虚函数,才能成为非抽象类。
实现—诸如添加一个空括号等

class A {
public:
    virtual void f() = 0; //纯虚函数
    void g() { this->f(); } //ok
    A() { } //f( ); // 错误
};
class B : public A {
public:
    void f() { cout << "B: f()" << endl; }
};
int main() {
    B b;
    b.g();
    return 0;
}

这里写图片描述

C++中的继承、多态虚函数是面向对象编程的重要概念。 继承是指一个类可以从另一个类继承属性和方法。子类可以继承父类的公有成员和保护成员,但不能继承私有成员。通过继承,子类可以重用父类的代码,并且可以添加自己的特定功能。继承可以实现代码的重用和层次化的设计。 多态是指同一个函数可以根据不同的对象调用不同的实现。多态可以通过虚函数来实现。虚函数是在基类中声明为虚拟的函数,它可以在派生类中被重写。当通过基类指针或引用调用虚函数时,实际调用的是派生类中的实现。这样可以实现动态绑定,即在运行时确定调用的函数。 虚函数的原理是通过虚函数表来实现的。每个包含虚函数的类都有一个虚函数表,其中存储了虚函数的地址。当调用虚函数时,编译器会根据对象的类型在虚函数表中查找对应的函数地址并调用。 综上所述,C++中的继承、多态虚函数是实现面向对象编程的重要机制,它们可以提高代码的灵活性和可扩展性。 #### 引用[.reference_title] - *1* *3* [C++多态虚函数虚函数表](https://blog.csdn.net/weixin_46053588/article/details/121231465)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [c++多态虚函数表内部原理实战详解](https://blog.csdn.net/bitcarmanlee/article/details/124830241)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值