多态与虚函数

1.何为多态???多态的作用??

多态的概念:
一个接口,多种方法

封装的作用:
封装可以是得代码模块化;继承可以扩展已经存在的代码,都是为了代代码重用;
多态的目的:接口重用

2.静态联编和动态联编分别表示什么?

在编译的时候能够确定对象所调用的成员函数的地址则为静态联编;
动态联编:指的是在程序运行的时候动态地进行,根据当时的情况来确定调用哪个同名函数,父类指针指向哪个子类,就调用哪个子类的同名函数,实际上是在运行的时候虚函数的实现;

3.类中有虚函数的时候,类有什么变化?

当类中存在虚函数的时候,这个类大小就增加4个字节,这个4字节是虚表指针,存放的是虚函数表的地址;
虚函数表其实是一个指针数组,它里面存放的其实是虚函数的地址;

虚函数的几个知识点

先看一个例子:

#include "stdafx.h"
#include <ostream>
#include <iostream>
using namespace std;

class CClassA {
public:
    virtual void fun_a() {
        cout << "fun:cclassA,这是类A里面的函数" << endl;
    }
};

class CClassB:public CClassA {
public:
    virtual void fun_a() {
        cout << "fun:cclassB,这是类B里面的函数" << endl;
    }
};

int main()
{
    CClassA objA, *pobjA;
    CClassB objB;
    pobjA = &objA;
    pobjA->fun_a();
    pobjA = &objB;//父类指针指向对象B,下面这个函数就是B里面的对象!!!
    pobjA->fun_a();
    return 0;
}

这里写图片描述

几个比较重要的知识点

一个空类:一个字节
一个空类里面只有一个整型,此类大小:4字节

1.定义基类的公有派生类,基类的公有派生类中重载该函数,重载该虚函数不是一般地重载,它要求函数名,返回类型,参数个数,参数类型和顺序完全相同,由于对虚函数进行了重载,派生类中的虚函数前的virtual关键字可以省略
2.父类虚表和子类虚表是2个独立的表(由于两个类之间有继承关系),故子类的虚表里面也有父类的虚函数指针,但是如果子类中有和父类中一样的函数名,那么子类中的虚函数就覆盖了子类虚表中所继承的父类虚函数地址;
3.如果父类中有虚函数,子类中无虚函数,则子类也会生成一个虚表,(因为子类要继承父类的虚函数,但是虚函数又不能随便放,只能生成一个虚表出来
4.纯虚函数是一种特殊的虚函数,是一种没有具体实现的虚函数

例子:
class  cclassA
{
virtual <函数类型><函数名>(<参数表>)=0;
}

含有纯虚函数的类是抽象类,抽象类是不能定义对象的(不能实例化)
含有纯虚函数的类—->抽象类——->不能定义对象!!!!!

析构函数为什么要推荐设计为虚函数

首先,先了解一下构造函数的调用顺序:
基类构造函数–>数据成员的构造函数->派生类构造函数
执行派生类的析构函数,也需要调用基类以及子对象的析构函数,析构顺序如下:
派生类析构函数–>数据成员类析构函数—–>基类的析构函数
正常情况下(没有虚析构的情况下),一个子类被释放的时候,会主动调用其父类析构函数;使用父类指针指向子类对象的时候,只会析构掉父类对象,如果此时子类里有堆空间内存,则会造成内存泄露!(内存泄露是指使用malloc或者new申请内存空间之后,没有freeh或者delete掉,此时申请的那块内存仍然处于占用状态,称为内存泄露!)
进一步解释:如果说你创建了一个类对象,并且也调用了它的构造函数,就相当于开辟了一段空间;如果说这段空间没有free或者说没有被delete掉,那么就会造内存泄漏!
其实出了函数之后,局部变量将自动销毁!
可以看下面这个例子(对上面例子的改动,把虚函数去掉了)来理解:

#include "stdafx.h"
#include <ostream>
#include <iostream>
using namespace std;

class CClassA {
public:
     void fun_a() {
        cout << "fun:cclassA,这是类A里面的函数" << endl;
    }
};

class CClassB :public CClassA {
public:
     void fun_a() {
        cout << "fun:cclassB,这是类B里面的函数" << endl;
    }
};

int main()
{
    CClassA objA, *pobjA;
    CClassB objB;
    pobjA = &objA;
    pobjA->fun_a();
    pobjA = &objB;//父类指针指向对象B,下面这个函数就是B里面的对象!!!
    pobjA->fun_a();
    return 0;
}

这里写图片描述

故必须将析构函数定义为虚函数,这样释放父类指针的时候便会调用子类的析构函数,也会正常释放掉子类的堆空间!
看下面一个例子:

#include "stdafx.h"
class Base
{
public:
    Base() {
        printf("base 父类构造\n");

    };
    virtual ~Base() {                   //1th在此行加不加virtual有一定区别,而对于一般成员函数,基类中有虚函数,则子类中对应的成员函数不一定声明为虚函数,因为子类继承了父类 
        printf("Base 父类析构\n");
    };
    void fun() {                        //2th
        printf("父类base-fun\n");
    };
};
class son :public Base
{
public:
    son() {
        printf("son 子类构造\n");
    };
     ~son() {                                        //**
        printf("son 子类析构\n");
    }
    void fun() {                         //3th
        printf("子类son-fun\n");
    };
};


int main()
{
    Base *p; //父类指针

    son *pobj = new son; //走到这一步,会先调用父类构造,然后再调用子类构造,new 出子类对象指针,从堆空间中分配出来的,一般创建对象是在栈空间里面

    p = pobj;   //父类指针指向子类对象            
    p->fun();          //注意,这个例子里面函数不是虚函数,所以调用的函数皆为父类里面的函数!!!!!
    delete pobj; //通过父类指针释放子类对象


    return 0;
}

这里写图片描述
**处不管有没有virtual这个关键词,结果都如下:
(验证了上面所说的:如果父类中有虚函数,子类中无虚函数,则子类也会生成一个虚表)
对上面例子进行改造:

#include "stdafx.h"
class Base
{
public:
    Base() {
        printf("base 父类构造\n");

    };
     ~Base() {                   //1th在此行加不加virtual有一定区别,而对于一般成员函数,基类中有虚函数,则子类中对应的成员函数不一定声明为虚函数,因为子类继承了父类 
        printf("Base 父类析构\n");
    };
    void fun() {                        //2th
        printf("父类base-fun\n");
    };
};
class son :public Base
{
public:
    son() {
        printf("son 子类构造\n");
    };
    ~son() {                                        //**
        printf("son 子类析构\n");
    }
    void fun() {                         //3th
        printf("子类son-fun\n");
    };
};


int main()
{
    Base *p; //父类指针

    son *pobj = new son; //走到这一步,会先调用父类构造,然后再调用子类构造,new 出子类对象指针,从堆空间中分配出来的,一般创建对象是在栈空间里面

    p = pobj;   //父类指针指向子类对象            
    p->fun();          //注意,这个例子里面函数不是虚函数,所以调用的函数皆为父类里面的函数!!!!!
    delete pobj; //通过父类指针释放子类对象


    return 0;
}

1th有了变化:
这里写图片描述
上面两个例子没什么变化

下面要注意:

将上面的例子中的1th改了:
Base *pobj = new son

#include "stdafx.h"
class Base
{
public:
    Base() {
        printf("base 父类构造\n");

    };
    virtual  ~Base() {                   //1th在此行加不加virtual有一定区别,而对于一般成员函数,基类中有虚函数,则子类中对应的成员函数不一定声明为虚函数,因为子类继承了父类 
        printf("Base 父类析构\n");
    };
    void fun() {                        //2th
        printf("父类base-fun\n");
    };
};
class son :public Base
{
public:
    son() {
        printf("son 子类构造\n");
    };
    ~son() {                                        //**
        printf("son 子类析构\n");
    }
    void fun() {                         //3th
        printf("子类son-fun\n");
    };
};


int main()
{
    Base *p; //父类指针

    Base *pobj = new son; //走到这一步,会先调用父类构造,然后再调用子类构造,new 出子类对象指针,从堆空间中分配出来的,一般创建对象是在栈空间里面

    p = pobj;   //父类指针指向子类对象            
    p->fun();          //注意,这个例子里面函数不是虚函数,所以调用的函数皆为父类里面的函数!!!!!
    delete pobj; //通过父类指针释放子类对象


    return 0;
}

这里写图片描述
对上面进行一些改动(在1th那边):


#include "stdafx.h"
class Base
{
public:
    Base() {
        printf("base 父类构造\n");

    };
      ~Base() {                   //1th在此行加不加virtual有一定区别,而对于一般成员函数,基类中有虚函数,则子类中对应的成员函数不一定声明为虚函数,因为子类继承了父类 
        printf("Base 父类析构\n");
    };
    void fun() {                        //2th
        printf("父类base-fun\n");
    };
};
class son :public Base
{
public:
    son() {
        printf("son 子类构造\n");
    };
    ~son() {                                        //**
        printf("son 子类析构\n");
    }
    void fun() {                         //3th
        printf("子类son-fun\n");
    };
};


int main()
{
    Base *p; //父类指针

    Base *pobj = new son; //走到这一步,会先调用父类构造,然后再调用子类构造,new 出子类对象指针,从堆空间中分配出来的,一般创建对象是在栈空间里面

    p = pobj;   //父类指针指向子类对象            
    p->fun();          //注意,这个例子里面函数不是虚函数,所以调用的函数皆为父类里面的函数!!!!!
    delete pobj; //这里没有释放子类对象,就是因为这个没有虚析构!

return 0;
}

这里写图片描述

虚基类

其的目的是为了解决二义性问题,使用公共基类在其派生类对象中只产生一个基类子对象!!!!!
自己的一个帖子壳参考参考:
有有关虚基类的知识

有关继承的一句话总结:

  • 公有继承方式:
    基类中的每个成员在派生类中保持同样的访问权限

  • 私有继承方式
    基类中的每个成员在派生类中都是private成员,而且它们不能在被派生的子类所访问

  • 保护继承方式
    基类中的public成员和protect成员在派生类中都是protect成员,private成员在派生类中仍然为private成员.

1.不管是什么继承方式,派生类的成员函数和友元函数都可以访问基类中的公有成员和保护成员,但是不能访问私有成员
2.在公有继承时,派生类的对象只能访问公有成员,在保护和私有类的继承时,派生类的对象不能访问基类任何成员

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值