详细了解C++多态

什么是多态??

1.多态性(polymorphism)据说最早来自希腊语,从字面上理解就是多种形态,多种形式。具体到C++这种面向对象的语言(OOP)中,其实就是一种“接口”,多种实现(方法)

2.多态可以分为静多态和动多态,具体的分类如下

详细了解C++多态_第1张图片

3.静多态和动多态的区别:

其实只是区别什么时间将函数实现和函数调用关联起来,是在编译期还是在运行期,即函数地址是在早绑定还是晚绑定的??

①静多态是指在编译期就可以确定函数的调用地址,并产生代码,这就是静态的,也就是说地址是早早绑定的,静多态往往也被叫做静态联翩。

②动多态则是指函数调用的地址不能在编译期间确定的,必须要在运行时才能确定,这就属于晚绑定,动多态往往也被叫做动态联翩。

4.具体实现

静多态往往是通过函数重载和模板(泛型编程)来实现,具体 见一下代码:

//两个函数构成重载的静多态

int add(int a,int b)
{
    return a + b;
}

double add(double a,double b)
{
    return a + b;
}

//构成函数重载的条件:相同函数名
//函数模板(泛型编程)
template 
//temolate
T add(T a,T b)
{
    return a + b;
}

多态存在的意义

C++中的几大特性:继承,封装,多态。封装可以是的代码模块化,继承可以使在原有的代码基础上扩展,他们的目的都是为了能够使代码复用。而多态是为了接口重用。也就是说不论传递过来的究竟是哪个类的对象(主要是:基类和派生类),函数都能通过同一个接口调用到适合各自对象的实现方法。

 

在具体讲解多态的前提下我们先来了解下面的知识点:

class Base{
public:
    void fun()
    {
        cout<<"Base::fun()"<

但是当我们放开了子类中fun函数的注释:

我们会发现基类对象依然调用Base::fun(),子类虽然继承了基类的fun但是子类本身中的fun构成了(重定义)隐藏,即基类中的fun被子类中的隐藏此时 我们子类中打印的是Derived::fun。

如我们想调用基类中被隐藏的成员函数:加上作用域d.Base::fun(),此时打印Base::fun()

 

依旧保持上面的类定义不变,我们来试试用指针来调用:

void FunTest()
{
    Base b;
    Derived d;
    Base* pb = &b;
    Derived* pd = &d;
    pb->fun();//pb指向基类打印Base::fun()
    pd->fun();//pd指向子类,打印Derived::fun()

    pb = &d;
    pb->fun();//基类指针指向子类对象,却打印Base::fun()

//同样得道理,对于引用
    Base& rb = b;
    Derived& rd = d;
    rb.fun();//rb引用基类,打印Base::fun()
    rd.fun();//rd引用子类,打印Derived::fun()

    Base& rd2 = d;//基类引用rd2引用子类,打印Base::fun()
    rd2.fun();
}
int main()
{
    FunTest();
    return 0;
}

我们知道C++继承中有复制兼容,即基类指针可以指向子类对象。那么为什么还会出现基类指针指向子类或者基类对象引用子类对象,却调用基类自己的fun打印函数?这就是我们上面讲的静态联翩导致的:在编译期就将函数实现和函数调用关联起来,不管是引用还是指针在编译期都是Base类型的所以其自然调用Base类的fun函数。

那么我们如何来实现让他调用子类中的fun函数呢??我们引入了动多态

所谓的动多态就是通过 继承+虚函数 来实现的,只有程序运行期间(非编译期间)才能判断所引用对象的实际类型,,根据其实际类型调用相应的方法。具体格式就是使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,并且派生类需要重新实现该成员方法,编译器实现动态绑定。

在上面的代码如果我们在基类的fun()函数前加上virtual即可实现动态绑定:

class Base{
   public:
    virtual void fun()
    {
        cout<<"Base::fun()"<

这样我们的子类指针调用的函数就是子类中的,基类指针指向子类对象也是调用子类中的成员函数

 

需要注意的是:【动态绑定的条件】

1.必须是虚函数(实现函数覆盖)

2.必须通过基类类型的指针或者引用来调用该虚函数(通过查找虚表,如果通过对象调用,直接就会在该对象的成员方法中寻找)

 

知识补充:

我们知道C++中虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。我们首先要区分几个概念:重载,重写(覆盖),以及重定义(同名隐藏)。

所谓重载是指在同一作用域中允许有多个同名函数,而这些函数的参数列表不同,包括参数的个数不一样,参数类型不同,参数的次序不同,需要注意的是返回值相同与否并不影响是否重载(返回值类型)。

而重写(覆盖)和重定义(同名隐藏)有点像,区别是在写重写的函数是否是虚函数,只有重写了 虚函数的才能算是体现了C++的多态性,否则为重定义

 

详细了解C++多态_第2张图片

纯虚函数

在成员函数的形参后面加上= 0;则成员函数为纯虚函数。包含纯虚函数类叫做抽象类(也叫做接口类),抽象类不能实例化出对象。纯虚函数必须在派生类中重新定义后,派生类才能实例化出对象

class Person
{
    public:
        virtual void display() = 0;
    private:
        string _name;
};

class Student:public Person
{
    void Display()
    {
        cout<<"Student"<

 抽象类往往用于以下情况,它可以方便我们使用多态特性,且很多情况下,基类本身生成对象是不合情理的,我们知道所有的对象都是通过类来描绘的,但是反过来不成立:并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就要使用抽象类,就像一个水果类可以派生出橘子,香蕉苹果等等,但是水果本身定义对象并不合理也没有必有。

要点

1.派生类重写基类的虚函数实现多态,要求函数名,参数列表,返回值完全相同。(协变除外)

2.基类中定义虚函数,派生类中该函数始终保持虚函数的特性。

3.只有类的非静态成员函数才能定义为虚函数,静态成员函数和友元函数不能定义为虚函数。

4.如果在类外定义虚函数,只有在声明函数时加上virtual关键字,定义时不用加。

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

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

7.最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数和基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)。

8.虚表是所有类对象实例公用的。

我们来看下面这组代码:

class A
{
public:
    virtual void Display()
    {}
};

class B
{
public:
    void Display()
    {}
};

int main()
{
    cout<

 我们知道空类占一个字节(为什么呢?),那为什么加了virtual关键字就变成了4?

这是因为每个有虚函数的类或者虚继承的子类,编译器都会为他们生成一个虚拟函数表(简称:虚表)表中的每个元素都会指向一个虚函数的地址。(注意虚表是从属的)

此外,编译器会为每个包含虚函数的类加上一个成员变量,是一个指向该虚函数表的指针( vptr),每一个由此类别派生出来的类,都有这么一个vptr。虚表指针都是从属对象的,也就是说该类如果含有虚表,则该类的所有对象都会含有一个虚表指针,并且该虚表指针指向同一个虚表,因此这里的4是指针的大小。

 

你可能感兴趣的:(C/C++,帅气,C++)