c++之多态

多态问题简单的概括就是“一个接口,多种方法”,程序在运行时才能决定调用的函数,它是面向对象编程领路的核心概念,字面意思就是多种状态。
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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值