c++之多态

来源:互联网 发布:1 10月进出口数据 编辑:程序博客网 时间:2024/06/06 18:14

多态问题简单的概括就是“一个接口,多种方法”,程序在运行时才能决定调用的函数,它是面向对象编程领路的核心概念,字面意思就是多种状态。
c++多态是用过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(或者称为重写),重写分为两种,直接重写成员虚函数和重写虚函数,只有重写了虚函数才算是体现了c++的多态性!!!而重载则是允许有多个同名函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同,编译器会根据这些函数的不同列表,江通明的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题,并不是多态!!
多态与非多态的实质区别就是函数地址的早绑定和玩绑定,如果函数的调用,在编译器的编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是地址是早绑定的。而如果函数调用的地址不能在编译期间确定,需要在运行时才确定,这就属于晚绑定。
多态的作用:封装可以使代码模块化, 继承可以扩展已存在的代码,他们的目的都是为了代码的重用,而多态的目的则是为了接口重用。无论传递过来的是哪个类的对象,函数都能通过同一个接口调用到适合各自对象的实现方法。
最常用的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法,如果没有使用虚函数,即没有利用多态,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态调用,函数调用的地址都是一定的,指向同一个函数,无法实现一个接口,多种方法的目的。
动态绑定的条件:
1. 必须是虚函数(在基类中是虚函数,在派生类中重写此虚函数)
2. 必须通过基类的引用或指针调用虚函数
静态绑定

#include<iostream>using namespace std;class base{    public:    void test()    {        cout>>"base::test()">>endl;    }    base& test1()    {    cout>>"base::test1">>endl;    }    private:    int _b;}class derived :public base{    virtual void test()    {    cout>>"derived::test()">>endl;    }    private:    int _d;}int main(){    base b;    derived d;    test(b);    test(d);}

不存在重写,所以两次调用test的结果是基类调用基类的test,派生类对象也是保存的基类的函数指针,调用基类的test函数

当在test前加上关键字virtual后:

#include<iostream>using namespace std;class base{    public:    virtual void test()    {        cout>>"base::test()">>endl;    }    private:    int _b;}class derived :public base{    virtual void test()    {    cout>>"derived::test()">>endl;    }    private:    int _d;}int main(){    base b;    derived d;    test(b);    test(d);}

输出的结果变成
base : : test()
derived : : test()
在函数重写时,派生类红重写的函数必须满足函数原型(返回值,函数类型,参数列表)相同,除协变(基类中的虚函数返回值类型为基类类型的指针或引用,派生类重写的虚函数返回派生类的指针或引用)

#include<iostream>using namespace std;class base{    public:    virtual void test()    {        cout>>"base::test()">>endl;    }    virtual base& test1()    {        cout>>"base::base&test1()"        return *this;    }    private:    int _b;}class derived :public base{    virtual void test()    {    cout>>"derived::test()">>endl;    }    virtual derived& test1()    {        cout>>"derived::derived&test1()"        return *this;    }    private:    int _d;}void test(){    b.test();    b.test1();}int main(){    base b;    derived d;    test(b);    test(d);}

输出结果为:
base::test()
base::base& test1()
derived::test()
derived::derived& test1()
虚函数使用的几点注意事项
1. 不要在构造函数和析构函数内部调用虚函数,在构造和西沟函数中,对象是不完整的,可能出现未定义的情况。调用虚函数的前提是对象一定构造成功,获取虚表地址必须通过对象的地址,如果对象创建失败,就无法获取虚表地址,因此构造函数不能定义为虚函数。
2. 在基类中定义了虚函数,则在派生类中该函数时钟保持虚函数的特性,在派生类中重写虚函数时可以省略virtual关键字。
3. 如果在类外定义虚函数,则只在勒种声明时加virtual关键字,在类外定义时不能加virtual关键字。
4. static静态函数不能定义为虚函数,惊天成员是该类的所有对象所共有,(可以通过类名或作用域限定符调用)可以不构建对象调用静态成员,但是虚表地址必须通过对象的地址才能获取。
5. 友元函数不能重写
6. 赋值运算符可以重写,但是最好不要重写,直接写成赋值运算符重载就可以。

int main(){base b1;derived d1;base&b2=b1; b2=b1;//基类的赋值运算符重载base& b3 =d1;//d1 = b2;//赋值兼容规则不允许子类给父类赋值,虽然有可能调用派生类的赋值运算符重载}
  1. 如果类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。

纯虚函数
在成员函数的列表后面加上=0,这个成员函数就变成了纯虚函数。
包含纯虚函数的类叫抽象类(也叫接口类),抽象类不能实例化出对象,纯虚函数在怕死恒磊中重定义后,派生类才能实例化出对象。
多态的对象模型
在一个有虚函数的类中,除了类的成员变量的大小以外,还会多出四个字节去保存一个虚表指针,这个虚表指针指向的空间就是虚函数的函数指针,以此来实现多态。
单继承
base的虚表指针
base的成员
derived的虚表指针
derived的成员
多继承:
base1的虚表指针
base1的成员
base2的虚表指针
base2的成员
derived的成员
菱形继承:
base1的虚表指针
base1的偏移量表地址
base1的成员
base2的虚表指针
base2的偏移量表地址
base2的成员
derived的成员
base类的虚表地址
base类成员变量
虚表存在的安全隐患:
效率低,调用一个函数需要查找两次,第一次得到的虚表地址,再在虚表中查看虚函数的地址,会造成性能降低
存在安全隐患,比如程序要求不能访问虚函数,但是仍可以在虚表中查看虚函数。

0 0