十六、多态、静态联编和动态联编、虚析构函数、三重

1.多态
2.多态意义
3.静态联编和动态联编
4.虚析构函数
5.重载、重写、重定义

1.什么是多态

1.1浅析多态的意义

如果有几个看上去相似但不完全相同的对象,有时人们要求在向它们发出同一个消息时,它们的反应各不相同,分别执行不同的操作。这种情况就是多态现象。

例如:甲乙丙3个班都是高二年级,它们有基本相同的属性和行为,在同时听到上课铃的时候,它们会分别走向3个不同的教室,而不会走向同一个教室

同样,如果有两支军队,当在战场上听到同种号声,由于事先约定不同,A军队可能实施进攻,而B军队可能准备防御

C++中所谓的多态(polymorphism)是指,由继承而产生的相关的不同的类,其对象对同一消息会作出不同的相应。

多态性是面向对象程序设计的一个重要特性,能增加程序的灵活度。可以减轻系统升级、维护、调试的工作量和复杂度

1.2赋值兼容(多态实现的前提)

(上一节兼容性规则部分)

赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。

赋值兼容是一种默认行为,不需要任何的显示的转化步骤。

赋值兼容规则中所指的替代包括以下的情况:

  • 派生类的对象可以赋值给基类对象。 ︎
  • 派生类的对象可以初始化基类的引用。
  • 派生类对象的地址可以赋给指向基类的指针。

在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。

class Parent
{
public:
   Parent(int a){
      this->a = a;
   }
   virtual void print(){
//virtual是flag后加的
//这里的virtual虚函数和上一节的virtual虚继承是两个完全不同的概念
      cout<<"Parent:print(): a = "<<a<<endl;
   }
private:
   int a;
};

class Child:public Parent
{
public:
   Child(int a,int b):Parent(a){

   }
  //重定义父类函数:发生在父类和子类之间
//当子类重写父类的成员函数,如果父类中这个函数不是虚函数,那么就是函数的重定义
//如果子类重写父类的成员函数,如果父类中的这个函数时虚函数。这是函数的重写
   virtual void print(){
//这里的virtual是为了可读性添加的,代表我重写了父类的方法!
//不写也不报错,但还是建议写
      cout<<"Child:print(): b = "<<b<<endl;
      Parent::print();
  }
private:
    int b;
};

//2015
class Child2:public Parent
{
public:
   virtual void print(){
         cout<<"Child2------.."<<endl;
    }
private:
    int c;
};


//1999 可以利用多态的特性来调用未来写的对象的方法
void myPrintFunc(Parent *p)//让父类指针指向子类对象的时候
{
     p->print();//在此时,print函数发生了多态现象
}

int main(void)
{
   Child c(10,20);
   c.print();//调用了Child::print()

    Parent p(100);
    p.print();//调用了Parent::print();

    cout<<"--------------------------"<<endl;
    myPrintFunc(&p);//希望调用父类的print
    cout<<"--------------------------"<<endl;
    myPrintFunc(&c);//希望调用子类的print(但是失败了),Parent *p = &c;父类指针指向子类对象是可以的,这里是调用了父类的print
    //编译器 会 不管你传递进来的父类对象还是子类对象,都会给你调用父类
    //编译器做了一个安全的举措,即使你传递的是子类,编译器也认为调用父类的print是安全的
    //它能够保证父类的print函数是一定存在的,没有毛线去调用子类的print
//flag--------------------------
//加上virtual关键字后,print成为了虚函数,当父类指针指向子类对象时,编译器不会调用父类方法,而是去调用子类方法

   Child2 c2;
   myPrintFunc(&c2);

   return 0;
}

1.3多态练习
class Hero
{
public:
   //当前hero的战斗力是10
    virtual int getAck(){
        return 10;
    }
};

//超级英雄
//多态具有调用未来的意义
class SuperHero:public Hero
{
public:
    virtual int getAck(){
        return 100;
    }
}




//怪兽
class Monster
{
public:
   int getAck(){
      return 30;
   }
};

//战斗的函数
void PlayerFight(Hero *hero,Monster *m)
{
//多态这种现象较 动态联编 是迟绑定 或是 晚绑定
   if(hero->getAck() > m->getAck(){//在此hero->getAck()就发生了多态
       cout<<"英雄战胜了怪兽"<<endl;
    }
   else{
      cout<<"英雄挂了"<<endl
   }
   
}


int main(void)
{

  Hero hero1;
  Monster mon1;

  SuperHero hero2;

  //开始战斗
  PlayerFight(&hero1,&m);

  PlayerFight(&hero2,&m);//Hero *hero = &hero2;


//指针的三个必要条件
 int a = 10;
 int *p = NULL;
 p = &a;
 *p;


//多态的三个必要条件
//1 要有继承
//2 要有子类重写父类的虚函数
//3 父类指针(或者引用)指向子类对象


  return 0;
}

2.多态工程的意义

封装:突破了C语言函数的概念。

继承:代码复用,复用原来写好的代码。

多态:

  • 多态可以使用未来,80年代写了一个框架,90人写的代码。
  • 多态是软件行业追寻的一个目标。

多态是设计模式的基础,多态是框架的基础

3.静态联编和动态联编

1、联编是指一个程序模块、代码之间互相关联的过程。
2、静态联编(staic binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编。
3、动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。switch语句和if语句是动态联编的例子。

1、C++与C相同,是静态编译型语言
2、在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认为父类指针指向的是父类对象。
3、由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象
从程序安全的角度,编译器假设父类指针只指向父类对象因此编译的结果为调用父类的成员函数。这种特性就是静态联编。
4、多态的发生是动态联编,实在程序执行的时候判断具体父类指针应该调用
的方法。

4.虚析构函数

  • 构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数。
  • 析构函数可以是虚的。虚析构函数用于指引delete运算符正确析构动态对象
class A
{
public:
   A(){
     cout<<"A()...."<<endl;
     this->p = new char[64];//给p开辟了空间
     memset(this->p,0,64);
     strcpy(p,"A construction");
   }

   virtual void print(){
        cout<<this->p<<endl;
   }

   virtual ~A(){
       cout<<"~A() ...."<<endl;
       if(p!=NULL){
          delete[] p;
          p = NULL;
       }
    }

private:
   char *p;
}

class B:public A
{
public:
   B(){
      cout<<"B()...."<<endl;
      this->p = new char[64];
      memset(this->p,0,64);
      strcpy(this->p,"B construction");
   }
   
   virtual void print(){
          cout<<this->p<<endl;
   }

   virtual ~B(){
       cout<<"~B() ...."<<endl;
       if(p!=NULL){
          delete[] p;
          p = NULL;
       }
    }

private:
   char *p;
}

class C:public B
{
public:
    C(){
      cout<<"C()...."<<endl;
      this->p = new char[64];
      memset(this->p,0,64);
      strcpy(this->p,"C construction");
   }
   
   virtual void print(){
      cout<<this->p<<endl;
   }

  virtual ~C(){
       cout<<"~C() ...."<<endl;
       if(p!=NULL){
          delete[] p;
          p = NULL;
       }
    }

private:
   char *p;
}


void func(A *p)
{
    p->print();//在此处发生多态
    delete p;//delete一个父类指针,崩了!这里把父类指针释放了
//如何让delete p不是将p看做父类指针来delte而是当子类呢?
//flag-------------------------
//改为虚析构函数
//如果类的析构函数加上virtual delte就会发生多态。delete p会调用C类的析构函数,之后就会自动依次析构了
//如果~() 不加 virtual关键字,不会发生多态,就只是调用父类析构函数
  
}

void test(){
  C c;//A->B->C陆续被构造
}//析构顺序 ~C-> ~B -> ~A

int main(void)
{
 // test();
  
  C *cp = new C;

  cp->print();//cp的函数

  delete cp;

   cout<<"---------------------------"<<endl;

   //C c;
   //func(&c);//这里改成虚析构之后,还是报错,因为这里的c是栈变量,出栈之后会再释放一次!

   C *cp1 = new C;//创建为堆的指针就可以防止栈释放了!
   func(cp1);
   return 0;
}

5.重载、重写、重定义

5.1重载(添加)
  • 相同的范围(在同一个类中)
  • 函数名字相同
  • 参数不同
  • virtual关键字可有可无
5.2重写(覆盖)

是指派生类函数覆盖基类函数,特征是:

  • 不同的范围,分别位于基类和派生类中
  • 函数的名字相同
  • 参数相同
  • 基类函数必须有virtual关键字
5.3重定义(隐藏)

是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

  • 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
  • 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容