码迷,mamicode.com
首页 > 其他好文 > 详细

多态练习

时间:2015-05-25 09:40:49      阅读:197      评论:0      收藏:0      [点我收藏+]

标签:

  1. 多态基础

    多态基本概念

    #include <iostream>

    using namespace std;

    //函数重写,在父类里有一个函数和在子类里有一个函数,他们的名字一致

    //赋值兼容性原则:将子类对象赋给父类指针或者引用

    //当函数重写遇上赋值兼容性,碰撞出了火花

    //面向对象的新需求

    //针对函数void howToPrintf(Parent *base)

    //如果传来父类对象,那么执行父类函数

    //如果传来子类对象,那么执行子类函数

    //一句话有多种形态

    //c++编译器给我们提供的方案是虚函数解决方案

    class Parent

    {

    public:

    ????Parent(int a = 0)

    ????{

    ????????this->a = a;

    ????}

    ????virtual void print() //虚函数解决方案

    ????{

    ????????cout << "a = " <<a << endl;

    ????}

    private:

    ????int a;

    };

    ?

    class Child : public Parent

    {

    public:

    ????void print()

    ????{

    ????????cout << "b = " << b << endl;

    ????}

    ????Child(int b = 0)

    ????{

    ????????this->b = b;

    ????}

    private:

    ????int b;

    };

    ?

    void howToPrint(Parent *base)

    {

    ????base->print();

    }

    ?

    void howToPrint(Parent &base)

    {

    ????base.print();

    }

    void main()

    {

    ????Parent p1;

    ????p1.print();

    ????Child c1;

    ????c1.print();

    ?

    ????Parent *base = NULL;

    ????base = &p1;

    ????p1.print();

    ????base = &c1;

    ????base->print(); //没有调用子类的函数

    ????Parent &p2 = c1;

    ????p2.print();

    ????howToPrint(&p1);

    ????howToPrint(&c1);

    ????howToPrint(p1);

    ????howToPrint(c1);

    ????//Child *child = NULL;

    ????//child = &p1;

    ?

    ????system("pause");

    }

    技术分享

    静态联编和动态联编

    将子类对象传递给父类指针或者引用,由于复制兼容性原则的存在,所以不会报错。

    c++为静态编译语言,根据指针指向的类型去执行相应类的函数,言外之意,是c++编译器根据指针的类型到具体的类里面执行相应的代码,不管传来的是父类对象,还是子类对象。

    如果在基类的函数加上virtual关键字,c++编译器会对该函数动一些手脚,产生了所谓的动态联编和迟绑定,这种现象综合起来,就是动态联编。

    静态联编函数的地址在在编译的时候就已经确定了,但是动态联编不同,动态联编会到函数运行的时候才会查找函数的地址。

    //英雄的战机hero Fight 10 --> adv 高级英雄战机

    //敌人的战机 enermy Fight 15 -->

    ?

    #include <iostream>

    using namespace std;

    ?

    class HeroFighter

    {

    public:

    ????virtual int AttackPower()

    ????{

    ????????return 10;

    ????}

    ?

    };

    ?

    class EnermyFighter

    {

    public:

    ????int DestoryPower()

    ????{

    ????????return 15;

    ????}

    ?

    };

    ?

    ?

    ?

    class AdvHeroFighter : public HeroFighter

    {

    public:

    ????int AttackPower()

    ????{

    ????????return 20;

    ????}

    ?

    };

    ?

    class BestFight : public AdvHeroFighter

    {

    public:

    ????BestFight();

    ????~BestFight();

    public:

    ????int AttackPower()

    ????{

    ????????return 50;

    ????}

    ?

    };

    ?

    //一个模型,如果你把这个函数放在框架里面呢?

    //面向对象三大概念

    //##封装##为什么牛逼?突破了c函数的概念

    //##继承##为什么牛啊?继承可以使用原来的代码-->代码复用

    //##多态##牛逼在哪里?下面的代码可以不变动

    //我写了一个框架,这个框架产生的时间

    //这个类产生的时间

    //我的框架可以调用未来的人写的代码,而且对之前的业务模型不做任何修改

    //这个层次比代码复用更高

    //我们可以调用未来

    ?

    //多态成立的三个条件

    //1.要有继承

    //2.要有虚函数重写

    //3.要有父类指针指向子类对象

    //三个条件缺一不可

    ?

    //间接赋值三个条件

    //1.定义两个变量,一个形参,一个实参

    //2.建立关联,实参取地址传给形参

    //3.*p(实参的地址)间接修改实参

    void ObjFighter(HeroFighter *pBase, EnermyFighter *pEnermy)

    {

    ????if (pBase->AttackPower() > pEnermy->DestoryPower())

    ????{

    ????????printf("猪脚win!\n");

    ????}

    ????else

    ????{

    ????????printf("猪脚挂掉了!\n");

    ????}

    }

    void play()

    {

    ????HeroFighter h1;

    ????EnermyFighter e1;

    ????AdvHeroFighter a1;

    ????if (h1.AttackPower() > e1.DestoryPower())

    ????{

    ????????printf("猪脚win!\n");

    ????}

    ????else

    ????{

    ????????printf("猪脚挂掉了!\n");

    ????}

    ?

    ????if (a1.AttackPower() > e1.DestoryPower())

    ????{

    ????????printf("猪脚win!\n");

    ????}

    ????else

    ????{

    ????????printf("猪脚挂掉了!\n");

    ????}

    }

    ?

    int main(int argc, char const *argv[])

    {

    ????HeroFighter h1;

    ????EnermyFighter e1;

    ????AdvHeroFighter a1;

    ????ObjFighter(&h1, &e1);//第一代战机上场

    ????ObjFighter(&a1, &e1);//第二代战机上场

    ????system("pause");

    ????return 0;

    }

    技术分享

    多态成立的三个条件:

    1.要有继承;

    2.要有虚函数重写;

    3.要有父类指针指向子类对象。

    三个条件缺一不可。

    面向对象三大概念

    封装为什么牛逼?

    因为它突破了c函数的概念,将与对象相关的刚发和属性都聚在一起。

    继承为什么牛啊?

    继承可以使用原来的代码,从而实现了代码的复用,减小了工作量。

    多态牛逼在哪里?

    打一个比方,我写了一个框架,这个框架产生的时间和这个类产生的时间,可以相差n年,但我的框架可以调用未来的人写的代码,而且对之前的业务模型不做任何修改这个层次比代码复用更高,这意味着我们可以调用未来。

  2. 多态进阶

    面向对象新需求及C++解决方案

    技术分享

    多态的实现效果

    多态:同样的调用语句有多种不同的表现形态。

    多态实现的三个条件

    有继承,有virtual重写,有父类指针(引用)指向子类对象。

    多态的C++实现

    virtual关键字,告诉编译器这个函数要支持多态;不要根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用

    多态的理论基础

    动态联编PK静态联编。根据实际的对象类型来判断重写函数的调用。

    多态的重要意义

    设计模式的基础。

    实现多态的理论基础

    函数指针做函数参数。

    C++中多态的实现原理

    当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类成员函数指针的数据结构,虚函数表是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。

    技术分享技术分享

    说明1:

    通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。

    说明2:

    出于效率考虑,没有必要将所有成员函数都声明为虚函数。

    如何证明vptr指针确实存在呢?

    当类中声明了virtual关键字的时候,c++编译器会自动给类加上一个vptr指针。

    #include "iostream"

    using namespace std;

    ?

    class A

    {

    };

    ?

    class B

    {

    public:

    ????virtual void print()

    ????{

    ????????cout << "B print run..." << endl;

    ????}

    ?

    };

    ?

    class C : public B

    {

    public:

    ????virtual void demo()

    ????{

    ????????cout << "C Demo run..." << endl;

    ????}

    };

    ?

    class D : public C

    {

    public:

    ????void print()

    ????{

    ????????cout << "D print run..." << endl;

    ????}

    protected:

    private:

    };

    ?

    void main()

    {

    ????A a;

    ????B b;

    ????C c;

    ????D d;

    ????cout << "sizeof(A)" << sizeof(a) << endl; //sizeof(A)1

    ????cout << "sizeof(B)" << sizeof(b) << endl; //sizeof(B)4

    ????cout << "sizeof(C)" << sizeof(c) << endl; //sizeof(C)4

    ????cout << "sizeof(D)" << sizeof(d) << endl; //sizeof(D)4

    ????d.print(); //D print run... 多态,父类某函数声明了virtual关键字,那么以后的子类以及子类的子类的该函数都是virtual类型

    ????system("pause");

    }

    由上面的例子可以看出很多东西,一个类即使什么东西也没有,他也会占用一个字节,当然,这个字节仅仅是占位符而已。函数是不占用类对象的存储空间的,但是给函数加上virtual关键字之后,类对象的存储空间到达了4字节,显然c++编译器给我们的类对象动了手脚,具体就是增加了一个vptr指针。

    构造函数中能调用虚函数,实现多态吗?why?

  3. 对象中的VPTR指针什么时候被初始化?

    对象在创建的时候,由编译器对VPTR指针进行初始化,只有当对象的构造完全结束后VPTR的指向才最终确定。

    父类对象的VPTR指向父类虚函数表

    子类对象的VPTR指向子类虚函数表

  4. 查看下面的例子:

    问题:在构造函数里能实现多态吗?

    ?

    #include "iostream"

    using namespace std;

    ?

    class AA

    {

    public:

    ????AA(int a = 0)

    ????{

    ????????this->a = a;

    ????????print(); //在构造函数里面能实现多态吗?

    ????}

    ?

    ????//分析一下要想实现多态,c++编译器应该动什么手脚

    ????//第一个需要动手脚的地方 起码这个函数print 我应该特殊处理

    ????virtual void print()

    ????{

    ????????cout << "parent: " << "a = " << a << endl;

    ????}

    protected:

    ????int a;

    };

    ?

    class BB : public AA

    {

    public:

    ????BB(int a = 0, int b = 0)

    ????{

    ????????this->a = a;

    ????????this->b = b;

    ?

    ????}

    ????virtual void print()

    ????{

    ????????cout << "child: " << "a = " << a << " b = " << b << endl;

    ????}

    private:

    ????int b;

    };

    ?

    ?

    void howToPrintf(AA *pBase)

    {

    ????//pBase 我怎么知道是父类对象还是子类对象

    ????//动手脚2::区分是父类对象还是子类对象,提前布局

    ????pBase->print(); //

    }

    void main()

    {

    ????//AA a1;

    ????BB b1;

    ????//howToPrintf(&a1);

    ????howToPrintf(&b1);

    ????system("pause");

    }

    技术分享

    vptr指针的初始化是分步完成的。

  5. 当指向父类的构造函数的时候,c++编译器会初始化子类的vptr指针,让vptr指针指向父类的虚函数表;
  6. 当父类的构造函数执行完毕之后,再执行子类的构造函数,这个时候,让vptr指针真正指向子类的虚函数表。
  7. 结论:构造函数中调用多态函数,不能实现多态。

    关于重载和重写

    #include <cstdlib>

    #include <iostream>

    using namespace std;

    ?

    /*重载发生在同一个类里,在编译期间就确定了函数的地址*/

    class Parent

    {

    public:

    ????Parent()

    ????{

    ????????cout << "Parent:Parent() run......" << endl;

    ????}

    public:

    ????void demo()

    ????{

    ????????cout << endl;

    ????}

    ????void func()

    ????{

    ????????cout << "Parent:void func() run......" << endl;

    ????}

    ?

    ????void func(int i)

    ????{

    ????????cout << "Parent:void func(int i) run......" << endl;

    ????}

    ?

    ????virtual void func(int i, int j)

    ????{

    ????????cout << "Parent:void func(int i, int j) run......" << endl;

    ????}

    };

    ?

    /*

    *重写-->父子类之间,函数三要素(函数名、函数参数、函数返回类型)完全一样

    *重写又分为两种

    *1.如果父类中有virtual关键字,这种父子之间的关系叫做虚函数重写,这种情况下发生多态 (动态链编 迟绑定)

    * 2.如果父类中没有virtual关键字,这种父子之间的关系 重定义(静态链编)

    */

    ?

    class Child : public Parent

    {

    ?

    public:

    ????/*此处2个参数,和子类func函数是什么关系*/

    ????void func(int i, int j) /*虚函数重写*/

    ????{

    ????????cout << "Child:void func(int i, int j) run......" << endl;

    ????}

    ?

    ????/*如何在子类中重载父类的func函数呢?

    ????*/

    ????/*此处3个参数的,和父类func函数是什么关系*/

    ????void func(int i, int j, int k) /*和父类的func没任何关系*/

    ????{

    ????????cout << "Child:void func(int i, int j, int k) run......" << endl;

    ????}

    };

    ?

    void run(Parent* p)

    {

    ????p->func(1, 2);

    }

    ?

    int main()

    {

    ????Parent p;

    ?

    ????p.func();

    ????p.func(1);

    ????p.func(1, 2);

    ?

    ????Child c;

    ????//c.func(); /*这里运行不了*/

    ????c.Parent::func(); /*运行父类的func函数*/

    ????c.demo(); /*与c.func()相对应的,demo是父类的一个函数*/

    ????c.func(1, 2);

    ?

    ????run(&p);

    ????run(&c);

    ?

    ????system("pause");

    ?

    ????return 0;

    }

    ?

    /*问题1:child对象继承父类对象的func,请问这句话能运行吗?why

    *c.func();

    *答案是运行不了,因为c++的隐藏规则

    *1.子类里面的func无法重载父类里面的func

    *2.当父类和子类有相同的函数名、变量名出现,发生名称覆盖

    *3.c.Parent::func();

    *问题2 子类的两个func和父类里的三个func函数是什么关系?

    */

    技术分享

    区分重载和重写,它们的特征如下:

    成员函数被重载的特征:

    (1)相同的范围(在同一个类中);

    (2)函数名字相同;

    (3)参数不同;

    (4)virtual关键字可有可无。

    重写的特征:

  8. 重写发生在父子之间;
  9. 函数三要素(函数名、函数参数、函数返回类型)完全一样。

    重写又分为两种:

  10. 如果父类中有virtual关键字,这种父子之间的关系叫做虚函数重写,这种情况下发生多态(动态链编,迟绑定);
  11. 如果父类中没有virtual关键字,这种父子之间的关系称为重定义(静态链编,隐藏)。

    C++的隐藏规则使问题复杂性陡然增加。这里"隐藏"是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

    (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。

    (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

    父子对象指针混搭风

    #include "iostream"

    using namespace std;

    ?

    //指针也是一种数据类型,指针数据的数据类型是指,它所指的内存空间的数据类型

    //最后一点引申 指针的步长 。。。c++

    ?

    class Parent

    {

    protected:

    ????int i;

    ????int???? j;

    public:

    ????virtual void f()

    ????{

    ????????cout << "Parent::f" << endl;

    ????}

    };

    ?

    ?

    class Child : public Parent

    {

    public:

    ????int k;

    public:

    ????Child(int i, int j)

    ????{

    ????????printf("Child:...do\n");

    ????}

    ?

    ????virtual void f()

    ????{

    ????????printf("Child::f()...do\n");

    ????}

    };

    ?

    void howToF(Parent *pBase)

    {

    ????pBase->f();

    }

    ?

    //指针的步长 在c++领域仍然有效,父类指针的步长和子类指针的步长不一样

    //多态是靠迟绑定实现的(vptr+函数指针实现)

    int main06()

    {

    ????int i = 0;

    ????Parent* p = NULL;

    ????Child* c = NULL;

    ?

    ????//不要把父类对象还有子类对象同事放在一个数组里面

    ????Child ca[3] = { Child(1, 2), Child(3, 4), Child(5, 6) };

    ?

    ????//不要用父类指针做赋值指针变量,去遍历一个子类的数组。

    ?

    ????p = ca;

    ????c = ca;

    ?

    ????p->f();

    ????c->f(); //有多态发生

    ?

    ????// ????p++;

    ????// ????c++;

    ????//

    ????// ????p->f();//有多态发生

    ????// ????c->f();

    ?

    ????for (i = 0; i < 3; i++)

    ????{

    ????????howToF(&(ca[i]));

    ????}

    ?

    ????system("pause");

    ????return 0;

    }

    我们要特别注意指向父类的指针和子类的指针,一般来说,它们的步长是不同的,一般来说,子类对象的指针比父类指针的步长要长(子类在父类的基础上增加了东西),因此上面的程序会出现错误,运行不了。

多态练习

标签:

原文地址:http://www.cnblogs.com/lishuhuakai/p/4527022.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!