精华内容
下载资源
问答
  • c++多态的作用
    千次阅读
    2018-11-19 22:04:46

    http://c.biancheng.net/view/265.html

    在面向对象的程序设计中,使用多态能够增强程序的可扩充性,即程序需要修改或增加功能时,只需改动或增加较少的代码。此外,使用多态也能起到精简代码的作用。本节通过游戏实例来说明多态的作用。
    游戏程序实例
    游戏软件的开发最能体现面向对象设计方法的优势。游戏中的人物、道具、建筑物、场景等都是很直观的对象,游戏运行的过程就是这些对象相互作用的过程。每个对象都有自己的属性和方法,不同对象也可能有共同的属性和方法,特别适合使用继承、多态等面向对象的机制。下面就以“魔法门”游戏为例来说明多态在增加程序可扩充性方面的作用。

    “魔法门”游戏中有各种各样的怪物,如骑士、天使、狼、鬼,等等。每个怪物都有生命力、攻击力这两种属性。怪物能够互相攻击。一个怪物攻击另一个怪物时,被攻击者会受伤;同时,被攻击者会反击,使得攻击者也受伤。但是一个怪物反击的力量较弱,只是其自身攻击力的 1/2。

    怪物主动攻击、被敌人攻击和实施反击时都有相应的动作。例如,骑士的攻击动作是挥舞宝剑,而火龙的攻击动作是喷火;怪物受到攻击会嚎叫和受伤流血,如果受伤过重,生命力被减为 0,则怪物就会倒地死去。

    针对:这个游戏,该如何编写程序,才能使得游戏版本升级、要增加新的怪物时,原有的程序改动尽可能少呢?换句话说,如何才能使程序的可扩充性更好呢?

    然而,无论是否使用多态,均应使每种怪物都有一个类与之对应,每个怪物就是一个对象。而且,怪物的攻击、反击和受伤等动作都是通过对象的成员函数实现的,因此需要为每个类编写 Attack、FightBack 和 Hurted 成员函数。

    Attack 成员函数表现攻击动作,攻击某个怪物并调用被攻击怪物的 Hurted 成员函数,以减少被攻击怪物的生命值,同时也调用被攻击怪物的 FightBack 成员函数,遭受被攻击怪物的反击。

    Hurted 成员函数减少自身生命值,并表现受伤动作。

    FightBack 成员函数表现反击动作,并调用被反击对象的 Hurted 成员函数,使被反击对象受伤。

    下面对比使用多态和不使用多态两种写法,来看看多态在提高程序可扩充性方面的作用。

    先介绍不用多态的写法。假定用 CDmgon 类表示龙,用 CWolf 类表示狼,用 CGhost 类表示鬼,则 CDragon 类的写法大致如下(其他类的写法与之类似):

    class CDragon
    {
    private:
    	int power;  //攻击力
    	int lifeValue;  //生命值
    public:
    	void Attack(CWolf * p);  //攻击“狼”的成员函数
    	void Attack(CGhost* p);  //攻击“鬼”的成员函数
    	//……其他 Attack 重载函数
    	//表现受伤的成员函数
    	void Hurted(int nPower);
    	void FightBack(CWolf * p);  //反击“狼”的成员函数
    	void FightBack(CGhost* p);  //反击“鬼”的成员函数
    	//......其他FightBack重载函数
    };
    各成员函数的写法如下:
    void CDragon::Attack(CWolf* p)
    {
    	p->Hurted(power);
    	p->FightBack(this);
    }
    void CDragon::Attack(CGhost* p)
    {
    	p->Hurted(power);
    	p->FightBack(this);
    }
    void CDragon::Hurted(int nPower)
    {
    	lifeValue -= nPower;
    }
    void CDragon::FightBack(CWolf* p)
    {
    	p->Hurted(power / 2);
    }
    void CDragon::FightBack(CGhost* p)
    {
    	p->Hurted(power / 2);
    }
    

    第 1 行,Attack 函数的参数 p 指向被攻击的 CWolf 对象。

    第 3 行,在 p 所指向的对象上面执行 Hurted 成员函数,使被攻击的“狼”对象受伤。调用 Hurted 成员函数时,参数是攻击者“龙”对象的攻击力。

    第 4 行,以指向攻击者自身的 this 指针为参数,调用被攻击者的 FightBack 成员函数,接受被攻击者的反击。

    在真实的游戏程序中,CDragon 类的 Attack 成员函数中还应包含表现“龙”在攻击时的动作和声音的代码。

    第 13 行,一个对象的 Hurted 成员函数被调用会导致该对象的生命值减少,减少的量等于攻击者的攻击力。当然,在真实的程序中,Hurted 成员函数还应包含表现受伤时动作的代码,以及生命值如果减至小于或等于零,则倒地死去的代码。

    第 17 行,p 指向的是实施攻击者。对攻击者进行反击,实际上就是调用攻击者的 Hurted 成员函数使其受伤。其受到的伤害的大小等于实施反击者的攻击力的一半(反击的力量不如主动攻击大)。当然,FightBack 成员函数中其实也应包含表现反击动作的代码。

    实际上,如果游戏中有 n 种怪物,CDragon 类中就会有 n 个 Attack 成员函数,用于攻击 n 种怪物。当然,也会有 71 个 FightBack 成员函数(这里假设两条龙也能互相攻击)。对于其他类,如 CWolf 类等,也是这样。

    以上为非多态的实现方法。如果游戏版本升级,增加了新的怪物“雷鸟”,假设其类名为 CThunderBird,则程序需要做哪些改动呢?

    除了编写一个 CThiinderBird 类外,所有的类都需要增加以下两个成员函数,用以对“雷鸟”实施攻击,以及在被“雷鸟”攻击时对其进行反击:

    void Attack(CThunderBird* p);
    void FightBack(CThunderBird* p);
    

    这样,在怪物种类多的时候,工作量会比较大。

    实际上,在非多态的实现中,使代码更精简的做法是将 CDragon、CWolf 等类的共同特点 抽取出来,形成一个 CCreature 类,然后再从 CCreature 类派生出 CDragon、CWolf 等类。但是由于每种怪物进行攻击、反击和受伤时的表现动作不同,CDmgon、CWdf 这些类还要实现各自的 Hurted 成员函数,以及一系列 Attack、FightBack 成员函数。因此,如果没有利用多态机制,那么即便引人基类 CCreature,对程序的可扩充性也没有太大帮助。

    下面再来看看,如果使用多态机制编写这个程序,在要新增 CThunderBird 类时,程序改动的情况。使用多态的写法如下:设置一个基类 CCreature,概括所有怪物的共同特点。所有具体的怪物类,如 CDragon、CWolf、CGhost 等,均从 CCreature 类派生而来。下面是 CCreature 类的写法:

    class CCreature {  //“怪物”类
    protected:
        int lifeValue, power;
    public:
        virtual void Attack(CCreature* p) {};
        virtual void Hurted(int nPower) {};
        virtual void FightBack(CCreature* p) {};
    };
    

    可以看到,基类 CCreature 中只有一个 Attack 成员函数,也只有一个 FightBack 成员函数。

    实际上,所有 CCreature 类的派生类也都只有一个 Attack 成员函数和一个 FightBack 成员函数。例如,CDragon 类的写法如下:

    class CDragon : public CCreature
    {
    public:
        virtual void Attack(CCreature* p) {
            p->Hurted(power);
            p->FightBack(this);
        }
        virtual int Hurted(int nPower) {
            lifeValue -= nPower;
        }
        virtual int FightBack(CCreature* p) {
            p->Hurted(power / 2);
        }
    };
    

    CDragon 类的成员函数中省略了表现动作和声音的那部分代码。其他类的写法和 CDragon 类类似,只是实现动作和声音的代码不同。如何实现动画的动作和声音不是本书要讲述的内容。

    在上述多态的写法中,当需要增加新怪物“雷鸟”时,只需要编写新类 CThunderBird 即可,不需要在已有的类中专门为新怪物增加 void Attack(CThunderBird * p) 和 void FightBack(CThunderBird* p) 这两个成员函数。也就是说,其他类根本不用修改。这样一来,和前面非多态的实现方法相比,程序的可扩充性当然大大提高了。实际上,即便不考虑可扩充性的问题,程序本身也比非多态的写法大大精简了。

    为什么 CDragon 等类只需要一个 Attack 函数,就能够实现对所有怪物的攻击呢?

    假定有以下代码片段:

    CDragon dragon;
    CWolf wolf;
    CGhost ghost;
    CThunderBird bird;
    Dragon.Attack(&wolf);
    Dragon.Attack(&ghost);
    Dragon.Attack(&bird);
    

    根据赋值兼容规则,上面第 5、6、7 行中的参数都与基类指针类型 CCreature* 相匹配,所以编译没有问题。从 5、6、7 三行进入 CDragon::Attack 函数后,执行 p-> Hurted(power) 语句时,p 分别指向的是 wolf、ghost 和 bird,根据多态的规则,分别调用的就是 CWolf::Hurted、CGhost::Hurted 和 CBird: Hurted 函数。

    FightBack 函数的情况和 Attack 函数类似,不再赘述。

    更多相关内容
  • 4. 多态用虚函数来实现,结合动态绑定。5. 纯虚函数是虚函数再加上= 0。6. 抽象类是指包括至少一个纯虚函数的类。 纯虚函数:virtual void breathe()=0;即抽象类!必须在子类实现这个函数!即先有名称,没内容,在...
  • C++多态的好处和作用(用实例说话) 在面向对象的程序设计中,使用多态能够增强程序的可扩充性,即程序需要修改或增加功能时,只需改动或增加较少的代码。此外,使用多态也能起到精简代码的作用。本节通过两个实例来...

    C++多态的好处和作用(用实例说话)

    在面向对象的程序设计中,使用多态能够增强程序的可扩充性,即程序需要修改或增加功能时,只需改动或增加较少的代码。此外,使用多态也能起到精简代码的作用。本节通过两个实例来说明多态的作用。
    游戏程序实例
    游戏软件的开发最能体现面向对象设计方法的优势。游戏中的人物、道具、建筑物、场景等都是很直观的对象,游戏运行的过程就是这些对象相互作用的过程。每个对象都有自己的属性和方法,不同对象也可能有共同的属性和方法,特别适合使用继承、多态等面向对象的机制。下面就以“魔法门”游戏为例来说明多态在增加程序可扩充性方面的作用。

    “魔法门”游戏中有各种各样的怪物,如骑士、天使、狼、鬼,等等。每个怪物都有生命力、攻击力这两种属性。怪物能够互相攻击。一个怪物攻击另一个怪物时,被攻击者会受伤;同时,被攻击者会反击,使得攻击者也受伤。但是一个怪物反击的力量较弱,只是其自身攻击力的 1/2。

    怪物主动攻击、被敌人攻击和实施反击时都有相应的动作。例如,骑士的攻击动作是挥舞宝剑,而火龙的攻击动作是喷火;怪物受到攻击会嚎叫和受伤流血,如果受伤过重,生命力被减为 0,则怪物就会倒地死去。

    针对:这个游戏,该如何编写程序,才能使得游戏版本升级、要增加新的怪物时,原有的程序改动尽可能少呢?换句话说,如何才能使程序的可扩充性更好呢?

    然而,无论是否使用多态,均应使每种怪物都有一个类与之对应,每个怪物就是一个对象。而且,怪物的攻击、反击和受伤等动作都是通过对象的成员函数实现的,因此需要为每个类编写 Attack、FightBack 和 Hurted 成员函数。

    Attack 成员函数表现攻击动作,攻击某个怪物并调用被攻击怪物的 Hurted 成员函数,以减少被攻击怪物的生命值,同时也调用被攻击怪物的 FightBack 成员函数,遭受被攻击怪物的反击。

    Hurted 成员函数减少自身生命值,并表现受伤动作。

    FightBack 成员函数表现反击动作,并调用被反击对象的 Hurted 成员函数,使被反击对象受伤。

    下面对比使用多态和不使用多态两种写法,来看看多态在提高程序可扩充性方面的作用。

    先介绍不用多态的写法。假定用 CDmgon 类表示龙,用 CWolf 类表示狼,用 CGhost 类表示鬼,则 CDragon 类的写法大致如下(其他类的写法与之类似):

    class CDragon
    {
    private:
        int power;  //攻击力
        int lifeValue;  //生命值
    public:
        void Attack(CWolf * p);  //攻击“狼”的成员函数
        void Attack(CGhost* p);  //攻击“鬼”的成员函数
                                 //……其他 Attack 重载函数
                                 //表现受伤的成员函数
        void Hurted(int nPower);
        void FightBack(CWolf * p);  //反击“狼”的成员函数
        void FightBack(CGhost* p);  //反击“鬼”的成员函数
                                    //......其他FightBack重载函数
    };
    

    各成员函数的写法如下:

    void CDragon::Attack(CWolf* p)
    {
        p->Hurted(power);
        p->FightBack(this);
    }
    void CDragon::Attack(CGhost* p)
    {
        p->Hurted(power);
        p->FightBack(this);
    }
    void CDragon::Hurted(int nPower)
    {
        lifeValue -= nPower;
    }
    void CDragon::FightBack(CWolf* p)
    {
        p->Hurted(power / 2);
    }
    void CDragon::FightBack(CGhost* p)
    {
        p->Hurted(power / 2);
    }
    

    第 1 行,Attack 函数的参数 p 指向被攻击的 CWolf 对象。

    第 3 行,在 p 所指向的对象上面执行 Hurted 成员函数,使被攻击的“狼”对象受伤。调用 Hurted 成员函数时,参数是攻击者“龙”对象的攻击力。

    第 4 行,以指向攻击者自身的 this 指针为参数,调用被攻击者的 FightBack 成员函数,接受被攻击者的反击。

    在真实的游戏程序中,CDragon 类的 Attack 成员函数中还应包含表现“龙”在攻击时的动作和声音的代码。

    第 13 行,一个对象的 Hurted 成员函数被调用会导致该对象的生命值减少,减少的量等于攻击者的攻击力。当然,在真实的程序中,Hurted 成员函数还应包含表现受伤时动作的代码,以及生命值如果减至小于或等于零,则倒地死去的代码。

    第 17 行,p 指向的是实施攻击者。对攻击者进行反击,实际上就是调用攻击者的 Hurted 成员函数使其受伤。其受到的伤害的大小等于实施反击者的攻击力的一半(反击的力量不如主动攻击大)。当然,FightBack 成员函数中其实也应包含表现反击动作的代码。

    实际上,如果游戏中有 n 种怪物,CDragon 类中就会有 n 个 Attack 成员函数,用于攻击 n 种怪物。当然,也会有 71 个 FightBack 成员函数(这里假设两条龙也能互相攻击)。对于其他类,如 CWolf 类等,也是这样。

    以上为非多态的实现方法。如果游戏版本升级,增加了新的怪物“雷鸟”,假设其类名为 CThunderBird,则程序需要做哪些改动呢?

    除了编写一个 CThiinderBird 类外,所有的类都需要增加以下两个成员函数,用以对“雷鸟”实施攻击,以及在被“雷鸟”攻击时对其进行反击:
    void Attack(CThunderBird* p);
    void FightBack(CThunderBird* p);
    这样,在怪物种类多的时候,工作量会比较大。

    实际上,在非多态的实现中,使代码更精简的做法是将 CDragon、CWolf 等类的共同特点 抽取出来,形成一个 CCreature 类,然后再从 CCreature 类派生出 CDragon、CWolf 等类。但是由于每种怪物进行攻击、反击和受伤时的表现动作不同,CDmgon、CWdf 这些类还要实现各自的 Hurted 成员函数,以及一系列 Attack、FightBack 成员函数。因此,如果没有利用多态机制,那么即便引人基类 CCreature,对程序的可扩充性也没有太大帮助。

    下面再来看看,如果使用多态机制编写这个程序,在要新增 CThunderBird 类时,程序改动的情况。使用多态的写法如下:设置一个基类 CCreature,概括所有怪物的共同特点。所有具体的怪物类,如 CDragon、CWolf、CGhost 等,均从 CCreature 类派生而来。下面是 CCreature 类的写法:

    class CCreature {  //“怪物”类
    protected:
        int lifeValue, power;
    public:
        virtual void Attack(CCreature* p) {};
        virtual void Hurted(int nPower) {};
        virtual void FightBack(CCreature* p) {};
    };
    

    可以看到,基类 CCreature 中只有一个 Attack 成员函数,也只有一个 FightBack 成员函数。

    实际上,所有 CCreature 类的派生类也都只有一个 Attack 成员函数和一个 FightBack 成员函数。例如,CDragon 类的写法如下:

    class CDragon : public CCreature
    {
    public:
        virtual void Attack(CCreature* p) {
            p->Hurted(power);
            p->FightBack(this);
        }
        virtual int Hurted(int nPower) {
            lifeValue -= nPower;
        }
        virtual int FightBack(CCreature* p) {
            p->Hurted(power / 2);
        }
    };
    

    CDragon 类的成员函数中省略了表现动作和声音的那部分代码。其他类的写法和 CDragon 类类似,只是实现动作和声音的代码不同。如何实现动画的动作和声音不是本书要讲述的内容。

    在上述多态的写法中,当需要增加新怪物“雷鸟”时,只需要编写新类 CThunderBird 即可,不需要在已有的类中专门为新怪物增加 void Attack(CThunderBird * p) 和 void FightBack(CThunderBird* p) 这两个成员函数。也就是说,其他类根本不用修改。这样一来,和前面非多态的实现方法相比,程序的可扩充性当然大大提高了。实际上,即便不考虑可扩充性的问题,程序本身也比非多态的写法大大精简了。

    为什么 CDragon 等类只需要一个 Attack 函数,就能够实现对所有怪物的攻击呢?

    假定有以下代码片段:

    CDragon dragon;
    CWolf wolf;
    CGhost ghost;
    CThunderBird bird;
    Dragon.Attack(&wolf);
    Dragon.Attack(&ghost);
    Dragon.Attack(&bird);
    

    根据赋值兼容规则,上面第 5、6、7 行中的参数都与基类指针类型 CCreature* 相匹配,所以编译没有问题。从 5、6、7 三行进入 CDragon::Attack 函数后,执行 p-> Hurted(power) 语句时,p 分别指向的是 wolf、ghost 和 bird,根据多态的规则,分别调用的就是 CWolf::Hurted、CGhost::Hurted 和 CBird: Hurted 函数。

    FightBack 函数的情况和 Attack 函数类似,不再赘述。
    几何形体程序实例
    例题:编写一个几何形体处理程序,输入几何形体的个数以及每个几何形体的形状和参数,要求按面积从小到大依次输出每个几何形体的种类及面积。假设几何形体的总薮不超过 100 个。

    例如,输入
    4
    R 3 5
    C 9
    T 3 4 5
    R 2 2

    表示一共有 4 个几何形体,第一个是矩形(R 代表矩形),宽度和高度分别是 3 和 5;第二个是圆形(C 代表圆形),半径是 9;第三个是三角形(T代表三角形),三条边的长度分别是 3,4,5;第四个是矩形,宽度和高度都是 2。

    应当输出:
    Rectangle:4
    Triangle:6
    Rectangle:15
    Circle:254.34

    该程序可以运用多态机制编写,不但便于扩充(添加新的几何形体),还能够节省代码量。程序如下:

    #include <iostream>
    #include <cmath>
    using namespace std;
    class CShape  //基类:形体类
    {
        public:
            virtual double Area() { };  //求面积
            virtual void PrintInfo() { }; //显示信息
    };
    class CRectangle:public CShape  //派生类:矩形类
    {
        public:
            int w,h;     //宽和高
            virtual double Area();
            virtual void PrintInfo();
    };
    class CCircle:public CShape  //派生类:圆类
    {
        public:
            int r;      //半径
            virtual double Area();
            virtual void PrintInfo();
    };
    class CTriangle:public CShape //派生类:三角形类
    {
        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.0;
        return sqrt(p * ( p - a)*(p- b)*(p - c));
    }
    void CTriangle::PrintInfo()  {
        cout << "Triangle:" << Area() << endl;
    }
    CShape *pShapes[100]; //用来存放各种几何形体,假设不超过100个
    int MyCompare(const void *s1, const void *s2)  //定义排序规则的函数
    {
        CShape **p1 = (CShape **)s1; //s1是指向指针的指针,其指向的指针为CShape* 类型
        CShape **p2 = ( CShape **)s2;
        double a1 = (*p1)->Area(); //p1指向几何形体对象的指针, *p1才指向几何形体对象
        double a2 = (*p2)->Area();
        if( a1 < a2 )
            return -1;   //面积小的排前面
        else if (a2 < a1)
            return 1;
        else
            return 0;
    }
    int main()
    {
        int i; int n;
        CRectangle *pr; CCircle *pc; CTriangle *pt;
        cin >> n;
        for( 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(i = 0;i <n;++i) {
            pShapes[i]->PrintInfo();
            delete pShapes[i]; //释放空间
        }
        return 0;
    }
    

    程序涉及三种几何形体。如果不使用多态,就需要用三个数组分别存放三种几何形体,不但编码麻烦,而且如果以后要增加新的几何形体,就要增加新的数组,扩充性不好。

    本程序将所有几何形体的共同点抽象出来,形成一个基类 CShape,CRectangle、CCircle 等各种几何形体类都由 CShape 类派生而来。每个类都有各自的计算面积函数 Area 和显示信息函数 PrintInfo,这两个函数在所有类中都有,而且都是虚函数。

    第 50 行定义了一个 CShape * pShapes[100] 数组。由于基类指针也能指向派生类对象,因此,每输入一个几何形体,就动态分配一个与该形体对应的类的对象(第 74、79、84 行), 然后将该对象的指针存入 pShapes 数组(第 76、81、86 行)。总之,pShapes 数组中的元素可能指向 CRectangle 对象,也可能指向 CCircle 对象,还可能指向 CTriangle 对象。

    第 90 行对 pShapes 数组进行排序。排序的规则是按数组元素所指向的对象的面积从小到大排序。注意,待排序的数组元素是指针而不是对象,因此调用 qsort 时的第三个参数是 sizeof (CShape *),而不是 sizeof(CShape)。

    在定义排序规则的 MyCompare 函数中,形参 s1(s2 与 s1 类似)指向的是待排序的数组元素,数组元素是指针,因而 s1 是指向指针的指针,其指向的指针是 CShape* 类型。*s1是数组元素,即指向对象的指针,**s1才是几何形体对象。

    由于 s1 是 void* 类型的,s1 无定义,因此要先将 s1 转换为 CShape* 类型的指针 p1(第 53 行),此后,p1 即是一个 CShape 类型的指针,*p1 指向一个几何形体对象,通过(p1)->Area()就能求该对象的面积了(第 55 行)。( p1) -> Area();这条语句是多态的,因为 *p1 是基类指针,Area 是虚函数。程序运行到此时,*p1 指向哪种对象,就会调用相应类的计算面积函数 Area,正确求得其面积。

    如果不使用多态,就需要将不同形体的面积一一求出来,存到另外一个数组中,然后再排序,排序后还要维持面积值和其所属的几何形体的对应关系——这显然是比较麻烦的。

    多态的作用还体现在第 91、92 行。只要用一个循环遍历排好序的 pShapes 数组,并通过数组元素调用 PrintInfo 虚函数,就能正确执行不同形体对象的 PrintInfo 成员函数,输出形体对象的信息。

    上面这个使用了多态机制的程序,不但编码比较简单,可扩充性也较好。如果程序需要增加新的几何形体类,所要做的事情也只是从 CShape 类派生出新类,然后在第 72 行的 switch 语句中加入一个分支即可。

    第 93 行释放动态分配的对象。按照本程序的写法,这条语句是有一些问题的。具体是什么问题,如何解决,将在《虚析构函数》一节中解释。

    展开全文
  • C++中的多态性具体体现在编译和运行两个阶段。编译时多态是静态多态,在编译时就可以确定使用的接口。运行时多态是动态多态,具体引用的接口在运行时才能确定。 静态多态和动态多态的区别其实只是在什么时候将函数...
  • 在面试过程中C++多态实现机制经常会被面试官问道。大家清楚多态到底该如何实现吗?下面小编抽空给大家介绍下多态的实现机制。 1. 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。 2. 存在虚...
  • c++多态

    2022-04-19 19:28:08
    c++多态

    多态用通俗点的话来说就是一种事物有多种形态,在c++中子类通常会在父类的基础上实现一些自己的功能。当我们需要通过调用函数类型的不同来实现不同功能时我们就会使用到多态。

    C++的多态必须满足两个条件:
    1 必须通过基类的指针或者引用调用虚函数
    2 被调用的函数是虚函数,且必须完成对基类虚函数的重写

    下面的代码就是多态的一个简单实现,当我们通过A类型的指针或者引用来调用函数时会根据new出来的对象类型产生不同的效果。

    #include<iostream>
    using namespace std;
    class A
    {
    public:
    	virtual void get() {
    		cout << "A" << endl;
    	}
    };
    class B :public A
    {
    public:
    	virtual void get() {
    		cout << "B" << endl;
    	}
    };
    int main()
    {
    	A* p = new B;
    	A* q = new A;
    	p->get();
    	q->get();
    	return 0;
    }

    如果上面的代码去掉virtual关键字输出的就会是两个A,就会形成所谓的静态多态,静态多态在编译期间编译器已经帮我们决定调用哪一个函数,所以相对的上面的代码就叫做动态多态。

    在父类中我们最好将析构函数加上virtual关键字,下面的程序没有加vitual

    #include<iostream>
    using namespace std;
    class A
    {
    public:
    	~ A() {
    		cout << "A" << endl;
    	}
    };
    class B :public A
    {
    public:
    	~ B() {
    		cout << "B" << endl;
    	}
    };
    int main()
    {
    	A* p = new B;
    	A* q = new A;
    	delete p;
    	delete q;
    	return 0;
    }

     我们可以看到我们析构一个B对象时并没有调用B的析构函数,这样就会引起内存泄漏。

    当我们加上virtual关键字后

     成功调用了B的析构函数,同时B再调用父类的析构函数。

    多态的原理就是在对象中增加一个虚函数表,同种类型的不同对象都公用一张虚函数表,虚函数表放在代码段,不需要动态开辟和销毁。可以自己在vs下的调试窗口查看虚函数表。

    当我们知道多态的原理后就会知道静态成员函数只是属于类的,不与任何对象相关联,所以静态成员函数是无法进行多态的。

    内联函数在添加virtual关键字后也会失去它的性质,因为虚函数需要调用时去虚函数表里找,而不是编译时展开。

    展开全文
  • C++多态的用法详解

    千次阅读 多人点赞 2022-04-21 16:38:26
    C++多态是在继承的基础上实现的,了解多态之前我们需要掌握一定的C++继承的知识,本文将介绍C++中多态的概念,构成条件以及用法。

    零.前言

    C++多态是在继承的基础上实现的,了解多态之前我们需要掌握一定的C++继承的知识,本文将介绍C++中多态的概念,构成条件以及用法。

    1.多态的概念

    多态,通俗来讲就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。比如,在买票这一行为,普通人买票是全价买票,学生买票是半价买票,而军人买票是优先买票;再比如动物园的动物叫这个行为,不同的动物叫声是不一样的。这些都是生活中多态的例子。

    2.C++中多态的分类

    (1)静态多态

    静态多态是指在编译时实现的多态,比如函数重载,看似是调用同一个函数其实是在调用不同的。
    比如我们使用cout这个对象来调用<<时:

    int i=1;
    double d=2.2;
    cout<<i<<endl;
    cout<<d<<endl;
    

    虽然调用的都是<<,但其实调用的是不同的操作符重载之后的函数。
    函数重载在之前的文章中详细讲解过,这里就不再赘述。

    (2)动态多态

    动态多态也就是我们通常所说的多态,本文以下内容均为动态多态内容。动态多态是在运行中实现的,
    当一个父类对象的引用或者指针接收不同的对象(父类对象or子类对象)后,调用相同的函数会调用不同的函数。
    这段话也许比较绕,这里只是给出一个概念,可以结合下面的例子来进行理解。

    3.多态的构成条件

    (1)举例

    我们先根据一个构成例子来理解多态构成的条件:

    #include<iostream>
    #include<string>
    using namespace std;
    class Person
    {
    public: 
    	virtual void BuyTicket()
    	{
    		cout << "全价买票" << endl;
    	}
    };
    class Student :public Person
    {
    public:
    	virtual void BuyTicket()
    	{
    		cout << "半价买票" << endl;
    	}
    };
    void Func(Person& p)
    {
    	p.BuyTicket();
    }
    int main()
    {
    	Person p1;
    	Student p2;
    	Func(p1);
    	Func(p2);
    }
    

    我们先来看这样一段代码,其中子类Student继承了父类。运行起来打印的结果是:
    在这里插入图片描述
    我们在反观上述中动态多态的定义,用父类的引用或者指针(这里使用的是Person& p)来接收不同类型的对象(p1和p2),该引用或指针调用相同的函数(都调用了p.BuyTicket()),都调用了各自类中不同的函数(打印的结果不同)。我们将这一过程称为动态多态。
    如果我们不传指针或者引用,那么将不构成多态(原理会在多态原理中详细解读)。

    (2)两个概念

    在解释多态的构成条件之前我们还需要了解两个概念。

    虚函数

    虚函数,即被virtual修饰的类成员函数称为虚函数。
    比如上面代码中父类和子类的成员函数就是虚函数。

    virtual void BuyTicket()
    	{
    		cout << "全价买票" << endl;
    	}
    

    关于虚函数还需要注意几点:

    1.普通的函数不能是虚函数,只能是类中的函数。
    2.静态成员函数不能加virtual

    总结起来就是只能是类的非静态成员函数才能去形成虚函数。

    虚函数的重写

    虚函数的重写又称为虚函数的覆盖(重写是表面意义上的,覆盖是指原理上的):派生类中有一个根基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型,函数名,参数列表完全相同),称子类的虚函数重写了父类的虚函数。

    //父类中的虚函数
    virtual void BuyTicket()
    	{
    		cout << "全价买票" << endl;
    	}
    //子类中的虚函数
    virtual void BuyTicket()
    	{
    		cout << "半价买票" << endl;
    	}
    

    两个虚函数满足返回值类型相同,函数名相同,参数列表相同。因此子类的虚函数重写了父类的虚函数。
    注意,只有虚函数才能构成重写。

    (3)多态的构成条件

    多态的构成满足两个条件:
    1.必须通过基类的指针或者引用调用虚函数。
    2.被调用的虚函数的派生类必须完成了对基类虚函数的重写。
    我们在来看上面的代码,确实满足该条件:

    1.使用了父类引用p来调用虚函数。
    2.派生类的虚函数完成了对基类的虚函数的重写。

    我们首先要明确使用多态的目的,就是使用不同的对象去完成同一个任务的时候会产生不同的结果。
    如果我们拿掉以上任何一个条件都不会再构成多态,比如我们不使用指针或者引用去接收对象从而调用虚函数,而是使用对象呢?

    void Func(Person p)
    {
    	p.BuyTicket();
    }
    

    此时我们会发现,打印的结果发生了变化:
    在这里插入图片描述
    这是不满足我们的预期的,因为不同的对象传给了p,p调用相同的函数却打印了相同的结果。
    我们还可以将更改参数列表或者将父类的virtual拿掉,发现依然不是我们想要的结果。
    但是有两个特殊的情况除外:

    4.虚函数重写的两个例外

    (1)协变

    如果我们将父类和子类中的虚函数的返回值设为不同,可能会发生如下报错:
    在这里插入图片描述
    协变指的是:派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类的虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
    简单来说就是两者的返回值是父子关系的指针或者引用。
    举一个例子:

    class A{};
    class B:public A
    	{};
    class Person
    {
    public: 
    	virtual A* BuyTicket()
    	{
    		A a;
    		cout << "全价买票" << endl;
    		return &a;
    	}
    };
    class Student :public Person
    {
    public:
    	virtual B* BuyTicket()
    	{
    		B b;
    		cout << "半价买票" << endl;
    		return &b;
    	}
    };
    

    我们将上一段代码进行了改写,定义了B继承A,而在Person和Student两个类中的虚函数中将返回值分别置为A和B,由于A和B是继承关系,所以仍然可以构成多态,我们称派生类的虚函数为基类的虚函数的协变。
    注意返回值必须是指针或者引用,对象不会构成协变。

    (2)析构函数的重写

    首先我们先回顾一下没有构成多态的析构函数调用:只需要子类对象销毁时无需手动销毁父类对象,会自动调用父类对象的析构函数。

    1.如果基类的析构函数为虚函数,此时子类的析构函数无论加不加virtual,都是对父类的析构函数的重写。
    2.虽然子类和父类的析构函数的函数名不同,但其实编译器对析构函数的名称进行了特殊的处理,都处理成了destructor。

    下面举例说明,将Person和Student写入析构函数:

    //父类中的析构函数
       virtual ~Person()
       {
    		cout << "~Person" << endl;
    	}
    //子类中的析构函数
    	virtual ~Student()
    	{
    		cout << " ~Student" << endl;
    	}
    //主函数
    	Person* p1 = new Person;
    	Person* p2 = new Student;
    	delete p1;
    	delete p2;	
    

    构成多态的结果是,Person*类型的p1和p2,接收两个不同类型的对象即Person类型和Student类型,在调用析构函数的时候可以分开调用(子类对象调用子类的析构函数,父类对象调用父类的析构函数。)
    我们将上述代码运行一下,会发现:
    在这里插入图片描述
    结果的确是如此,当析构父类对象时,调用父类的析构函数,当析构子类对象时,调用的是子类的析构函数和父类的析构函数。
    如果我们不使用父类指针进行管理,而是使用对象来接收子类对象呢?

    	Student p2;
    	Person p3 = p2;
    

    此时我们发现打印的结果是:
    在这里插入图片描述
    在析构p3的时候,并没有根据按Student类的规则来进行析构。
    同时,当我们将派生类的virtual去掉的时候,仍然可以构成多态,这与底层原理有关,在下面的介绍中会提及。为了统一性,不建议将virtual拿掉,C++大佬为了防止发生不必要的内存泄漏,所以设置了这一规则。这就导致所有的其实派生类的所有虚函数virtual都可以省略。这是由于其继承了基类的virtual属性,具体的还要在底层去理解,再强调一遍,尽量不要在派生类中省略virtual。

    5.final与override

    (1)final

    限制类不被继承

    但我们想要设计一个不被继承的类时,目前我们知道的有一种方法:就是将父类的构造函数设为私有(这是因为子类需要调用父类的构造函数来进行初始化)。如果使用这种方式,定义父类对象的话需要使用单例模式。
    final提供了另一种方式来限制一个类不会被继承。
    只需要在不想被继承的类后加final即可:

    class Person final
    {
    public: 
    	virtual A* BuyTicket()
    	{
    		A a;
    		cout << "全价买票" << endl;
    		return &a;
    	}
    	virtual ~Person()
       {
    		cout << "~Person" << endl;
    	}
    };
    

    此时如果子类去继承Person的话会报错。

    限制虚函数不被重写

    当我们在函数后加上final的时候,该虚函数将不能被子类中的虚函数重写,否则会发生报错。

    	virtual A* BuyTicket() final
    	{
    		A a;
    		cout << "全价买票" << endl;
    		return &a;
    	}
    

    (2)override

    将override放在子类的重写的虚函数后,判断是否完成重写(重写的是否正确)

    	 virtual B* BuyTicket(int i=10) override
    	{
    		B b;
    		cout << "半价买票" << endl;
    		return &b;
    	}
    

    注意:final关键字放在父类的位置,override关键字放在子类的位置。

    6.抽象类

    在虚函数的后面加上=0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫做接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象。**只有重写虚函数,派生类才能实例化出对象。**注意虽然不能实例化出对象,但是可以定义指针。
    抽象类的存在本质上来说就是希望我们在派生类中重写父类的虚函数。抽象类中的虚函数一般只声明,不实现,因为没有意义。我们可以搭配override来使用。

    //将父类中写入纯虚函数,父类变成抽象类
    class Person
    {
    public: 
    	virtual A* BuyTicket() =0//纯虚函数
    	{
    		A a;
    		cout << "全价买票" << endl;
    		return &a;
    	}
    	virtual ~Person()
       {
    		cout << "~Person" << endl;
    	}
    };
    

    此时子类必须只有重写虚函数才能定义对象。通常情况下现实中没有的事物,定义成抽象类会比较合适。
    虽然我们不能使用抽象类来定义对象,但是我们可以使用抽象类来定义指针。

    class Car
    {
    public:
    	virtual void Drive() = 0
    	{
    		cout << " Car" << endl;
    	}
    };
    class Benz :public Car
    {
    public:
    	virtual void Drive()
    	{
    		cout << "Benz" << endl;
    	}
    	void f()
    	{
    		cout << "f()" << endl;
    	}
    };
    int main()
    {
    	//Car* p = nullptr;
    	//p->Drive();//程序会崩溃
    	Car* a = new Benz;
    	a->Drive();
    }
    

    我们可以使用父类指针去接收子类对象,同时调用函数。但是不能使用父类去创建对象。

    7.总结

    C++多态的目的在于当我们使用父类的指针或者引用去接收子类的对象后,接收不同的子类对象的父类指针或者引用调用的相同的函数产生的结果不同。
    重点在于实现多态的几个条件:
    一是用父类的指针或者引用来接收。
    二是子类必须对父类的虚函数进行重写。
    三。。。三连支持一下博主叭球球了。

    展开全文
  • C++类的多态练习(Class polymorphism exercise)
  • c++ 多态详解

    2022-05-02 15:58:41
    c++多态详解
  • C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用...
  • C++ 多态的游戏例程

    2021-01-20 03:35:15
    1)头文件 game.h #ifndef GAME_H #define GAME_H // base class class CCreature { protected: int m_nLifePower, m_nPower; public: virtual void Attack(CCreature *pCreature){} virtual void Hurted...
  • C++ 多态 超详细讲解

    万次阅读 多人点赞 2021-05-16 11:46:58
    文章目录多态概念引入1、C++多态的实现1.1 多态的构成条件1.2 虚函数1.3虚函数的重写1.4 C++11 override && final1.5 重载,覆盖(重写),重定义(隐藏)2、抽象类2.1 抽象类的概念2.2 接口继承和实现继承3...
  • c++多态总结

    2018-08-30 13:25:38
    c++多态总结,vptr总结,多态原理探究
  • C++多态

    千次阅读 2020-10-20 17:15:01
    C++多态前言动态多态多态的构造多态的使用多态底层剖析解答纯虚数函数和抽象类虚析构和纯虚析构总结 前言 多态是C++面向对象三大特性之一 多态分为两类 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名 ...
  • C++多态Demo

    2019-05-01 09:52:12
    长方形和三角形均继承于形状,拥有高和宽,分别求面积
  • C++多态的理解

    千次阅读 2022-03-15 17:11:05
    c++的多态性具体体现在编译和运行两个阶段。编译时多态是静态多态,在编译时就可以确定使用的接口。运行时多态是动态多态,具体引用的接口在运行时才能确定。 静态多态和动态多态区别其实只是在什么时候将函数实现...
  • C++多态全方面详解。

    千次阅读 2022-05-02 16:25:10
    什么是多态 顾名思义多态就是多种形态的意思。 例如:去游乐场玩,儿童买票是半价,成年人买票是全价,老年人买票打七折。 对比类的话,这个买票就是一个类的一个方法(函数)。儿童,成年人,老年人即是对象,...
  • c++ 多态的实现及原理

    2022-05-13 19:12:40
    多态就是多种形态,C++多态分为静态多态与动态多态。静态多态就是重载,因为在编译期决议确定,所以称为静态多态。动态多态就是通过继承重写基类的虚函数实现的多态,因为是在运行时决议确定,所以称为动态多态。...
  • C++ 程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。...在C++中主要分为静态多态和动态多态两种,在程序运行前就完成联编的称为静态多态,主要通
  • c++多态的基本概念

    2022-05-17 09:50:17
    c++多态的基本概念
  • C++ 多态的简单例子

    2022-01-17 16:28:34
    // 多态测试 参数是指针类型或引用类型 void drawShape(Shape* s) {// (Shape &s) s->Draw(); //s->Draw(); } int main() { // 同一个函数根据参数的不同类型体现出不同的结果 cout; Circle c; drawShape(&c);// ...
  • C++多态详解

    千次阅读 2021-06-29 22:54:31
    C++三特性:继承,封装,多态。什么是多态? - 多态分为静态多态(编译阶段)和动态多态(运行阶段) - 静态多态:函数重载和泛型编程 - 动态多态:虚函数 :根据绑定的类型调用响应的函数执行! 动态多态依靠虚...
  • C++多态的底层原理

    千次阅读 多人点赞 2022-04-24 22:09:26
    要了解C++多态的底层原理需要我们对C指针有着深入的了解,这个在打印虚表的时候就可以见功底,理解了多态的本质我们才能记忆的更牢,使用起来更加得心应手。
  • c++多态常见面试题

    2022-04-29 14:52:11
    c++常见面试题
  • C++多态理解与认识

    2022-01-24 23:40:24
    1.什么是多态多态是指函数调用的多种形态,使我们调用函数更加灵活。 多态分为静态多态与动态多态 1)静态多态:静态多态指的是编译时的多态,通过函数重载实现。根据函数命名规则找到函数地址,从而实现调用不同...
  • 基于C++多态的企业职工系统大作业实现代码和讲义
  • c++多态类动物的叫声

    2021-06-12 22:06:24
    c++多态类动物的叫声
  • C语言实现C++多态

    2021-11-15 22:12:42
    C语言实现C++多态 C++中多态实现是基于虚函数表实现的,每个具备多态性对象的内部都会有一个隐藏的虚函数表,虚函数表里面的函数指针指向具体的函数实现,可能是父类中的实现,或是子类重写了的方法。C语言没有天然...
  • C++ 多态与虚函数

    千次阅读 2022-03-24 17:15:50
    C++中有两种多态性: 编译时的多态 通过函数的重载和运算符的重载来实现的 运行时的多态性 运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 126,209
精华内容 50,483
关键字:

c++多态的作用

c++ 订阅