C++-多态,纯虚函数,抽象类,工厂模式,虚析构函数(day10)

  一、多态(更多见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)<<endl; 
  }

};



class Derived:public Base{

public:

  int cal(int a,int b){

    return a*b;
  }

}



int main(void){

  Derived d;

  Base b=d;

  cout<<b.cal(100,200)<<endl;//调用的是基类中的版本,不会形成多态调用

  d.foo();//子类对象中是没有foo函数的,但是由于传进去的this虽然是Base*,但是指向的是Derived*类型的&d,所以在调用时,相当于this->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

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

猜你喜欢

转载自www.cnblogs.com/ptfe/p/11299869.html