多态与多态对象模型

多态即多种形态。多态分两种

  • 编译时多态:在编译期间编译器根据函数实参的类型确定要调哪个函数(这个我们之前已经接触过了,和函数重载差不多,是同名函数,但是参数不同)

  • 运行时多态:在程序运行期间才会去判断到底会调用哪个函数。这里我们主要讲的就是动态多态。

C++怎么实现多态?(动态多态,静态多态,函数多态,宏多态)

  • 动态多态通过虚函数和继承实现
  • 静态多态通过泛型来实现
  • 函数多态通过函数重载来实现
  • 宏多态通过宏替换来实现

 

函数的重写

  • 被重写的那个函数一定要是虚函数(即父类中的那个函数必须是虚函数,在父类中是虚函数的情况下,子类中重写的那个函数可以不加上virtual关键字)

  • 重写的两个函数一定是一个在父类,一个在子类,子类的虚函数对父类的虚函数进行重写

  • 两个函数的函数名和参数列表必须相同,一模一样!

 

多态的形成条件

  • 子类有对父类中的虚函数重写

  • 有一个父类的指针/引用来调用重写的虚函数(指向父类调父类的,指向子类调子类的)

 

注意:如果不构成多态,即不满足上面的两个条件,具体调用哪个函数是根据调用者的类型来确定的。一旦构成多态,具体调用哪个函数则是根据调用者的对象来确定的(因为父类的指针/引用可以指向子类的对象)

 

通过下面的代码来感受一下多态

class Person

{

public:

        virtual void BuyTickets()

        {

               cout << "普通人全票" << endl;

        }

protected:

        string _name;

};

class Student :public Person

{

public:

        virtual void BuyTickets()

        {

               cout << "学生半票" << endl;

        }

};

void Test(Person& p)

{

        p.BuyTickets();

}

int main()

{

        Person p;

        Student s;

        Test(p);

        Test(s);

        return 0;

}

 

上述的结果则是Test(p)输出“普通人全票”,Test(s)输出“学生半票”,这两个函数构成了多态。

 

那么,注意,假设这两个函数没有构成重写,即假设,把两个函数virtual关键字去掉,这两个函数其实是构成了重定义,即子类中的函数重定义了父类中的函数。那么这个时候会输出什么呢?

注意这个时候没有构成多态,那么就不看调用者到底是什么对象了,而是看调用者是什么类型。这里Test函数中的参数p是一个Person类型,那么调的就是父类的函数,即输出两个“普通人全票”。

 

这个时候,我们假设子类中的函数是虚函数,父类中的函数不是虚函数,即子类函数有关键字virtual,父类没有,构不构成多态呢?

注意到这个时候并不构成多态。

 

那么反过来呢?即父类中函数有virtual关键字,子类函数中没有,会怎样?

这种情况下其实是构成多态的,函数重写是默认支持这样的方式的,即子类中的函数默认支持父类的虚函数属性。但是这样的写法是不被提倡的,最好还是在两个函数中都加上virtual关键字。

 

 

关于多态的一些总结

  • 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同(协变除外),注意这里返回值也必须要相同,协变的话返回值可以不同,下面是一个协变的例子

class A

{

public:

        virtual A* f1()

        {

               cout << "A::f1()" << endl;

               return this;

        }

};

class B :public A

{

public:

        virtual B* f1()

        {

               cout << "B::f1()" << endl;

               return this;

        }

};

上面的这种情况就是协变,能够构成多态,即一个虚函数返回值可以是父类的指针或引用,一个虚函数可以是子类的指针或引用

  • 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性(即可以不加virtual关键字,但最好加

  • 只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数(静态成员不能被继承

  • 如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。

  • 构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆

  • 不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。

  • 最好将基类的析构函数声明为虚函数。(这是为了正确地释放子类对象,考虑到A* p = new B();delete p;

    如果父类的析构函数不是虚函数,那么delete的时候会去调用父类的析构函数,这样就子类对象中属于子类的那部分就没有被释放(父类那部分调用父类析构函数释放了)。如果父类析构函数是虚函数,那么子类的析构函数会默认继承父类析构函数的虚函数属性,虽然两者的名字不一样,但在这里是一个特殊,在汇编里面的名字其实已经变了的,仍然构成重写。这样的话,如果父类的指针指向的是父类的对象就会调用父类的析构函数,父类的指针指向子类的对象就会调用子类的析构函数,从而构成多态),下面就是析构函数在汇编中的名字

        

  • 虚表是所有类对象实例共用的

 

重载、重写、重定义的区别

 

返回值

函数名

作用域

参数列表

其他

重载

可以相同,可以不同

相同

同一作用域

必须不同

访问修饰符可以不同,可以一个是公有的,一个是私有的

重写

相同(协变除外)

相同

父子作用域

必须相同

父类函数必须有virtual关键字

 

访问修饰符可以不同

重定义

可以相同,可以不同

相同

父子作用域

可以相同,可以不同

在父子作用域中相同名字的,不是重写就是重定义

纯虚函数

     在成员函数形参后面写上=0,则该成员函数就是纯虚函数。包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化出对象。这个纯虚函数声明出来就是让子类来重写的,在子类中重写了这个纯虚函数之后,才能够实例化出对象。纯虚函数不用在父类中初始化

class A

{

public:

        virtual void f1()=0;

};

class B :public A

{

public:

        virtual void f1()

        {

               cout << "B::f1()" << endl;

        }

};

 

 

虚表(虚函数表)

        对于有虚函数的类,编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针(注意和菱形继承虚继承中的虚基表的区别)

class Base

{

public:

        virtual void func1()

        {

               cout << "Base::func1()" << endl;

        }

private:

        int _a;

};

我们先看看这个Base类型的对象的大小,我们本身期望的是4个字节(注:这是在32位平台下)

void Test1()

{

        Base b;

        cout << sizeof(Base) << endl;

}

结果是8个字节,这多出来的4个字节就是指向存放虚表位置的指针。只要该对象模型有了虚函数,那么编译器就会为这个类维护一张存放虚函数的表。注意虚函数并不是放在虚表中的,虚函数还是放在代码段,指向虚函数的指针放在虚表中。

 

接下来看下重写了虚函数和没有重写虚函数的差别

 

首先来看下如果没有虚函数的重写

class Base

{

public:

        virtual void func1()

        {

               cout << "Base::func1()" << endl;

        }

        virtual void func2()

        {

               cout << "Base::func2()" << endl;

        }

};

class Derive :public Base

{

public:

        virtual void func3()

        {

               cout << "Derived::func3()" << endl;

        }

        virtual void func4()

        {

               cout << "Derived::func4()" << endl;

        }

};

 

typedef void(*ptr)();//设一个函数指针

 

void PrintVTable()//通过这个函数来输出函数在内存中分布的地址

{

        Base b;

        for (int i = 0; i < 2; ++i)

        {

               ptr p = (ptr)(*((int*)*(int*)&b + i));

               p();

               cout << ":" << (int*)p << endl;

        }

 

        cout << endl;

 

        Derive d;

        for (int i = 0; i < 4; ++i)

        {

               ptr p = (ptr)(*((int*)*(int*)&d + i));

               p();

               cout << ":" << (int*)p << endl;

        }

}

 

再来看看有虚函数重写的虚表

class Base

{

public:

        virtual void func1()

        {

               cout << "Base::func1()" << endl;

        }

        virtual void func2()

        {

               cout << "Base::func2()" << endl;

        }

};

class Derive :public Base

{

public:

        virtual void func1()//重写父类的虚函数func1

        {

               cout << "Derived::func1()" << endl;

        }

        virtual void func4()

        {

               cout << "Derived::func4()" << endl;

        }

};

 

typedef void(*ptr)();//设一个函数指针

 

void PrintVTable()//通过这个函数来输出函数在内存中分布的地址

{

        Base b;

        for (int i = 0; i < 2; ++i)

        {

               ptr p = (ptr)(*((int*)*(int*)&b + i));

               p();

               cout << ":" << (int*)p << endl;

        }

 

        cout << endl;

 

        Derive d;

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

        {

               ptr p = (ptr)(*((int*)*(int*)&d + i));

               p();

               cout << ":" << (int*)p << endl;

        }

}

 

 

看完了上面的单继承,再来看看多继承的(给一串代码,给一个模型)

class Base1

{

public:

        virtual void fun1()

        {

               cout << "Base::fun1" << endl;

        }

        virtual void fun2()

        {

               cout << "Base::fun2" << endl;

        }

private:

        int b1;

};

 

class Base2

{

public:

        virtual void fun1()

        {

               cout << "Base::fun1" << endl;

        }

        virtual void fun2()

        {

               cout << "Base::fun2" << endl;

        }

private:

        int b2;

};

 

class Derive :public Base1, public Base2

{

public:

        virtual void fun1()

        {

               cout << "Derive::fun1" << endl;

        }

        virtual void fun3()

        {

               cout << "Derive::fun3" << endl;

        }

private:

        int d;

};

      

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值