博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++-多态,纯虚函数,抽象类,工厂模式,虚析构函数(day10)
阅读量:5226 次
发布时间:2019-06-14

本文共 3241 字,大约阅读时间需要 10 分钟。

  一、多态(更多见day9)

1、多态条件

1)多态特性除了要在基类中声明虚函数,并在子类中形成有效的覆盖,还必须通过指针或者引用来调用虚函数,才能表现出来,直接通过对象无法进行多态调用。

2)调用虚函数的指针也可以是this指针,只要它是一个指向子类对象的基类指针,同样可以表现出多态的特性。

class Base{public:  virtual int cal(int x,int y){    retunr x+y;  }  //d.foo()-->foo(&d)  //void foo(Base* this)  //Base* this =&d;  void foo(void){    cout<
<
cal(100,200),故形成多态调用  return 0;}

 


 

二、纯虚函数、抽象类、纯抽象类

1、纯虚函数

 

virtual void draw(void)=0;//纯虚函数

 

纯虚函数的一般形式如下:

virtual 返回类型 函数名(参数列表)[const]=0;

 

2、抽象类

  1)如果一个类中包含了纯虚函数,那么它就是抽象类。例如,前述的Shape类就是一个抽象类,类并不包含具体的行为。所以编译器不允许抽象类实例化对象。如果实例化,会出现下面的错误:

  2)另外,如果继承过来的基类具有纯虚函数,并且子类不做覆盖的话,那么子类也将变成抽象类。

  3)如果一个类中的所有成员函数(不包括构造函数,析构函数)都是纯虚函数,那么这个类就叫做纯抽象类。

3)工厂模式举例

 

Team1负责解析 class PDFParse{public:  void prase(const char* pdffile)//解析图形,文本,图片函数  {    OnCircle();    OnRect();    Ontext();    OnImage();    //。。。  }private:  virtual void Oncircle(void)=0;  virtual void OnRect(void)=0;  virtual void OnText(void)=0;  virtual void OnImage(void)=0;};Team2负责绘图实现class PDFRender:public PDFParse{private:  void OnCircle(void){    ...  }  void OnRect(void){    ...  }     void OnText(void){    ...  }  void OnImage(void){    ...  };};int main(void){  PDFRender render;  render.parse("something.pdf");//通过this指针实现多态  return 0;}

 

4)多态实现的原理(了解)

  1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。  

  2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。  

  3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。  

  4:多态用虚函数来实现,结合动态绑定.  

  5:纯虚函数是虚函数再加上 = 0;  

  6:抽象类是指包括至少一个纯虚函数的类。

 

 

 

 实现原理:  

(1)对于编译器来说,非虚函数的调用地址在在编译的时候就被绑定,这样的绑定称为早期绑定。如果是非虚函数,即使子类的指针或者引用向上造型到基类的指针或引用,调用同名函数时也只是调用基类中的函数,因为这在编译阶段就已经绑定好了,在执行时无法改变。

(2)在(1)中如果要使用基类的指针调用子类的函数,就要使用带虚函数

(3)在任何一个类中如果有一个虚函数,那么就会为这个类添加一个虚函数表指针(三级指针,函数指针一般为一级指针,虚表中的后半部分为虚函数指针数组,可知它为二级指针),指向虚函数表(简称虚表),如上图,foo(),虚表指针在对象中占四个字节大小,虚表不属于对象,而是通过虚表指针来取其中的内容。图中,基类中foo()函数为虚函数,子类继承之后也为虚函数,并且都存在虚表,并且覆盖了A类的foo()函数的起始地址,添加上自己类的地址。而bar函数没有被重写,则原封不动的继承该函数,起始地址也不变。

(4)虚表指针vptr属于晚绑定,会根据实际指针指向的类型或引用的实际类型进行调用。每个对象的虚函数的调用都是通过虚函数来进行索引的,就像数组有起始地址和下标一样。所以虚表指针的正确初始化就显得十分重要。那么虚表指针是何时进行创建和初始化的呢?虚表指针其实是在构造函数中被创建和初始化的,(3)中也说了,虚表指针属于对象的一部分,占4个字节的内存大小,所以在构造函数中初创建和初始化显得理所当然。

(5)在构造时,先要构造基类的基类子对象,编译器看到父类具有虚函数,就创建和初始化基类的虚表,当构造子类对象时,发现了子类的虚函数,就对基类需要覆盖的虚表进行覆盖,就如foo()函数被覆盖,但是bar()函数没有被覆盖。并且在子类中会有自身的虚表指针(区别于基类的虚表指针),这就是为什么通过指针或者引用调用时可以实现多态的原因。而直接使用对象调用时,不用虚表指针进行索引,直接调成员函数(通过基类的对象调用父类的函数(这个函数实现了虚函数,内部其实是this指针起了作用)除外)。

 


 

 

 三、虚析构函数

1、引入

  一个指向子类对象的基类指针进行析构的时候,只能调用基类的析构函数,而无法调用子类的析构函数,前面学到的方法是把基类的指针做一个向下造型进行析构。实际中并不这样做,而是使用虚析构函数来解决。

 

 

class Base{

public:

  Base(void){

    cout<<"Base::Base()"<<endl;

  }

   virtual ~Base(void){

    cout<<"Base::~Base()"<<endl;

  }

};

class Derived:public Base{

public:

  Derived(void){

    cout<<"Derived::Derived()"<<endl;

  }

  ~Derived(void){

    cout<<"Derived::~Derived()"<<endl;

  }

};

 

int main(void){

  Base* pb=new Derived;//如果不声明为虚析构函数,这种方式释放内存将有内存泄漏风险

  delete pb;

  return 0;

}

 

  如果基类的析构函数为虚函数,那么子类的析构函数也是虚函数,可以对基类的析构函数进行有效覆盖。这时候再delete指向子类的基类指针,实际调用的是子类的析构函数,而子类的析构函数又会调用基类的析构函数,这样可以避免上述问题。

 


 

 

四、练习--薪资计算

 

员工属性:姓名,工号,职位级别,绩效工资,出勤率

经理:绩效奖金(元/月)

技术员:研发津贴(元/小时)

销售员:提成比率(百分比)

 

薪资=基本工资+绩效工资

基本工资=职位级别额度*出勤率

 

 绩效工资:因职位不同而异

 

普通员工:基本工资一半

经理:绩效奖金*绩效因数(手动输入)

技术员:研发津贴*工作小时数*进度因数(手动输入)

销售员:销售额度(手动输入)*提成比例

技术主管:(技术员绩效工资+经理的绩效工资)/2

销售主管:(销售员绩效工资+经理绩效工资)/2

 

结果:打印员工数据,输入必须输入的数据,计算薪资

 

转载于:https://www.cnblogs.com/ptfe/p/11299869.html

你可能感兴趣的文章
母牛的故事(hdu2018)——笔记待完善
查看>>
sql 按天及上午下午分组
查看>>
RMQ
查看>>
Springboot中如何在Utils类中使用@Autowired注入bean
查看>>
logback配置异步日志
查看>>
KeleyiTextarea类
查看>>
JQuery调用WCF服务,部署在iis
查看>>
C#邮件发送(含附件)
查看>>
命令行记录
查看>>
64位中使用AWE分配内存
查看>>
【uoj#175】新年的网警 结论题+Hash
查看>>
串口数据缓存java版
查看>>
以太坊源码(03):POA委员会选举机制
查看>>
算法之动态规划初步(Java版)
查看>>
浅谈三层架构(2)
查看>>
UVA 753 A Plug for UNIX 电器插座(最大基数匹配,网络流)
查看>>
HYSBZ 1588 营业额统计 (Splay树)
查看>>
《视觉SLAM十四讲》学习日志(一)——预备知识
查看>>
python读取单个文件操作
查看>>
OO电梯调度
查看>>