对多态的缠缠绵绵

多态:

概念:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性
简单概括是:一个接口,多种结果。

c++的多态性是通过虚函数实现的,虚函数是允许子类成员函数重写父类成员函数,当然父类成员函数前要加virtual关键字,子类可以不加,但是为了提高代码的清晰性,建议你最好加上。而子类重新定义父类成员函数的做法叫做覆盖(或者重写)。

只有重写了虚函数才可以体现c++的多态性,

有多态就有非多态,它们的区别是函数地址是晚绑定还是早绑定,那么问题又来了,早绑定和晚绑定是什么?
早绑定就是程序在编译阶段就可以确定函数的地址并生产代码,这是静态的。
晚绑定就是函数地址是在程序运行阶段确定的,这是动态的。
理解到这里我觉的还是不够,早绑定不就像静态联编吗?晚绑定就像动态联编。

下来我们来了解静态联编和动态联编
静态联编:静态联编是指联编工作是在编译阶段进行的,又叫做早期联编,在程序运行之前完成,要实现静态联编,在编译阶段必须确定程序中的函数调用,确定函数调用就必须确定函数地址,这就涉及了早绑定问题。静态联编对函数的选择是看对象的指针或者引用的类型。

eg:
class A
{
    public:
    void func()
    {
        cout<<"funcA"<<endl;
    }

};

class B : public A
{
    public:
     void func()
    {
        cout<<"funcB"<<endl;
    }
};
void Fun(A &a)
{
    a.func();
}
int main()
{
    A a;
    B b;
    Fun(a);
    Fun(b);
    system("pause");
}

结果:
这里写图片描述
从上面代码可以看出,在Fun函数中创建的对象类型是父类,并且通过对象的引用调用普通成员函数,这里仅仅与引用的类型有关,而与此时引用指向的对象无关。所以在main函数中不同的对象调用其实都调用的是父类对象

动态联编:动态联编是程序在运行阶段动态进行的,根据当时情况确定调用那个重名函数,这就是需要虚函数的实现了,又叫做晚期联编。动态联编对成员函数的选择是基于指向对象的类型,针对不同的对象类型会产生不同的结果。多态性和虚函数就体现了动态联编的特性。

那么构成动态联编必须满足下面的条件(也就是构成多态的条件)

  1. 当父类使用指针或引用指向子类的对象并调用相应的虚函数
  2. 子类与父类构成重写(即把重名函数定义为虚函数)
    这里必须是父类对象的指针,因为虚函数会根据传入的指针或引用的对象类型来调用不同的函数,实现不同的功能,父类指针可以指向子类对象(可以进行切片处理),子类指针不能指向父类对象(因为子类不是完全继承父类,父类中私有成员是不可见的,是不能继承的)
class A
{
public:
    virtual void func()
    {
        cout << "funcA" << endl;
    }

};

class B : public A
{
public:
    virtual void func()
    {
        cout << "funcB" << endl;
    }
};
     void Fun(A &a)
{
     a.func();
}
int main()
{
    A a;
    B b;
    Fun(a);
    Fun(b);
    system("pause");
    return 0;
}

这样结果如下:

这里写图片描述
从上面代码看出,父子类中的重名函数func定义为虚函数时,但引用指向不同的对象,就执行不同的操作。这里既体现了多态性(同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果)。
总结一下:如果没有多态性,函数调用的地址是一定的,是静态的,静态的地址
调用时会一直调用同一个函数,而具有多态性了,函数调用的地址就是动态的,调用时就可以根据指向对象类型调用不同的函数,这就体现了一个接口,不同结果的目的。

多态和虚函数的总结:

  1. 子类重写父类的虚函数实现多态时,必须函数名,参数列表,返回值相同(协变除外)
  2. 父类中定义了虚函数,子类中该函数依然保持虚函数特性(就算子类中不写virtual)
  3. 只有类的成员函数才可以定义为虚函数
  4. 静态成员函数不能定义为虚函数
  5. 如果在类外定义虚函数,只能在声明时加virtual,不能在定义时加virtual
  6. 构造函数不能定义为虚函数,最好不要将operator=定义为虚函数,因为容易引起误会,并且没有必要,它们不会构成重写,因为返回类型不同。
  7. 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为。
  8. 必须把父类的析构函数定义为虚函数

    协变是什么:子、父类虚函数返回类型不同,但返回类型都是子、父类的指针这样就构成了协变。

这里写图片描述

解释总结5:总结3说:只有类的成员函数才可以定义为虚函数,所以在类外不能定义为虚函数,在类外定义虚函数编译器会自动报错的。所以你声明在类里的虚函数,定义在类外的虚函数就会出现总结3说的错误。
解释6:虚函数的指针或地址放在虚表里的,因为对象还没有产生,虚表还没有初始化出来。
解释8:看下图

这里写图片描述

此时new B动态开辟出来一个子类对象,子类对象B指向父类指针p,因此在释放时,只调用了父类的析构函数~A,对象B没有释放,就存在内存泄漏问题。
这里写图片描述
而父类析构函数定义为虚函数时,就会将A,B对象都释放。
delete p;在这里它们构成了多态,为什么构成了多态?
因为析构函数在底层被处理成:p->destructor + operator delete,它们的函数名时相同的,并且父类为虚函数,还使用父类指针指向子类对象,满足多态要求,所以构成多态,所以delete指向子类对象,先析构完子类对象,因为子类继承了父类,所以再析构父类对象。所以必须把父类的析构函数定义为虚函数,否则会产生内存泄漏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值