类的继承,多态学习总结

 在面向对象里面有个非常重要的概念:继承与派生,这两个其实说的是一回事。

现在我引入一个新名词:“可重用性”。

   而重用性好指的是有一部分代码实现了某个功能,而当你要新写一个新代码(新代码也要实现相同功能),这个时候你就不用重新再写,你把原来的代码拿来用就行了。

继承:
在你要定义一个新的类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),
那么就可以把A作为一个基类,而把B作为基类的一个派生类(也称子类)。而A与B·重复的部分我们就不用再写一遍了。

 派生类拥有基类的全部特点,并且派生类是通过对基类进行修改和扩充得到的。
 “扩充”,就是在派生类中,可以扩充新的成员变量和成员函数。
   “修改”,就是在派生类中,编写与基类函数名一样的函数,而这个函数的功能与原来的不同,这就是修改。


派生类一经定义后,可以独立使用,不依赖于基类。这句话什么意思呢?
它说的是:“当你用派生类定义对象的时候,可以放心大胆的使用,这个时候基类 ‘ 与我无关!’ 。”

注意事项:
(1) 派生类拥有基类的全部成员函数和成员变量,不论是pr ivate、protected、 public。
(2)在派生类的各个成员函数中,不能访问基类中的private成员。

派生类对象中的内存问题:

派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。(注意这个自己)在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。

!派生类的创造函数

 在创建派生类的对象时:
 (1) 先执行基类的构造函数,用以初始化派生类对象中从基类继承的成员;
 (2)再执行成员对象类的构造函数,用以初始化派生类对象中成员对象。
 (3)最后执行派生类自己的构造函数

!派生类的析构函数

 在派生类对象消亡时:
 1)先执行派生类自己的析构函数
 2)再依次执行各成员对象类的析构函数
 3)最后执行基类的析构函数

注意!!!----》析构函数的调用顺序与构造函数的调用顺序相反。

 开始新的知识点:虚函数和多态

!多态可以说是面向对象里面一个非常重要的机制。它主要是用来提高程序的可扩充性。现在先不解释可扩充性什么意思,卖个关子,在此先“声明”,至于“定义”就留到后头。

敲黑板,重点来了!拿笔和本子开始记了

!!!虚函数:

(1)在类的定义中,前面有virtual 关键字的成员函数就是虚函数。
例如:
class base {

virtual string get();
//注意这个“virtual”
};

int base:get() { }

(2)virtual 关键字只用在类定义里的函数声明中,l类外写函数体时不需要用到关键字了。

(3)注意了(!!!):构造函数和静态成员函数一定不能是虚函数。

什么是多态?

首先我们要知道派生类的指针可以赋给基类指针。
那么通过基类指针调用基类和派生类中的同名虚函数时:

(1) 若该指针指向一个基类的对象时:
那么被调用是基类的虚函数;

(2) 若该指针指向一个派生类的对象时:
那么被调用的是派生类的虚函数。

这种机制就叫做“多态”。当然还有其他的方法表示多态,这个方式我称它为:
“指针多态大法”, 这么样?有没有种武功秘籍的感觉?

既然说是“武功秘籍”了,那么就一定有练武奇才,还有练武 “不太奇” 才,没关系啦!在我这那是包学包会,一个不也拉下,把你们从学海深处都给捞上来。

有没有同志觉得这个新知识点和我前面提到的不一样啊?它这么就这么“关谷神奇”呢?这么就能区别要调用同名里的哪一个?

      我觉得解释再多也不如来场实战,举个例子吧。
class Human{
          private:
            略
         public:
         virtual void gorw_up(){年龄加一}
};
 class Human_adults:public Human{
       private:
         略
       public:
       vietual void gorw_up(){年龄加一}
 };
 int main() {
    Human_adults  xiaoM;
    
    Human *it = &  xiaoM;  //  基类指针指向派生类对象

   p  -> gorw_up();   //  用基类指针调用同名函数
 //   重点!!! 调用哪个虚函数取决于p指向哪个类型的对象
   return 0;
}

派生类的对象还可以赋给基类引用

通过基类引用调用基类和派生类中的同名虚函数时:

(1)若该引用引用的是一个基类的对象,那么被调用是基类的虚函数;

(2)若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数。

这种机制也叫做“多态”,同理,这个叫作:“引用多态大法”。至于两种大法哪个更强,看个人情况吧,就得自己适合哪个,就用哪个。(没有无敌的功法,只有无敌的人!)

接着实战:
class Xiaoshuo{
        private:
            略
         public:
         virtual void yanqing(){}
};
class SanGuoYy:public Xiaoshuo{
        private:
            略
         public:
              virtual void yanqing(){}
};
int main(){
  sanguoYy a;
  Xiaoshuo &it = a;   //  基类引用派生类对象
  it.yanqing();   // 调用哪个虚函数取决于it引用哪个类型的对象。
  return 0; 
}

!还有看不懂的吗?应该还是有滴。没事,不要气馁,郭靖不也是大器晚成类型的吗?咱最不怕不懂,就怕不肯努力!功夫不到家,没事,那就先不回家,他奈我何?

 class A [

  public :
  virtual void Print( )  {cout << "A::Print"<<endl ;}
  };

  class B: public A[

  public :
  virtual void Print( )  { cout << "B::Print" <<endl; }
  };

  class D: publicA[

  public:
  virtual void Print( ) { cout «< "D::Print" << endl ;}
  };

  class E: public B 
           public:

  virtual vỏid Print( ){ cout << "E::Print" << endl ;}
  };
  
 int main(){
    A a;
    B b; 
    E e;
    D d;
     A * pa= &a; 
     B * pb=  &b;
     D * pd= &d;
     E * pe= &e;
    pa->Print();   //a.Print()被调用,输出: A::Print
       pa= pb;
    pa -> Print();  // b.Print()被调用,输出: B::print
       pa= pd;
    pa -> Print();  // d.Print()被调用,输出:D::Print
       pa = pe;
   pa -> Print();  //  e.Print()被调用,输出:E::Print
   return 0;
}

说了这么久多态,那么多态的作用是什么?读书为了金榜题名,练武为了冲锋陷阵,那么学习多态为了啥啊?

在面向对象的程序设计中使用多态,能够增强程序的 “ 可扩充性 ” ,即程序需要修改或增加功能的时候,只需要改动和增加较少的代码即可(课本是这么说的,虽然很枯燥无味!)

用句通俗的话来说,多态就是为了偷懒而生滴。我们只需要知道:多态可以让一个函数可以分情况实现多种功能就行了。

!学了虚函数,就有比较特殊的虚函数等着你哦。现在我介绍这个虚函数里的奇葩之一:“虚析构函数”。
------什么是虚析构函数?从名字就能看出来:虚函数+析构函数。就这么简单!

!学习它之前,我先说下什么情况下用它,别学了半天发现:“学这个有啥用?”

在通过基类的指针删除派生类对象时,通常情况下只(没错,你发现了吧,“只”)调用基类的祈构函数。
(这个析构不完整)

但是,(注意这个但是!)删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数。

解决办法 ---->>  把基类的析构函数声明为virtual,而派生类的析构函数可以virtual不进行声明。

(你知道为什么吗?)
因为基类的析构函数已经声明为:virtual,那么派生类的析构函数就自动被声明为:virtual

通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数.

也许有好奇宝宝问了:既然有虚析构函数,那么有没有虚构造函数?
???很遗憾的告诉你:没有!而且不允许以虚函数作为构造函数!!!

我觉得多半有人对我上面那句:“这个析构不完整”感到疑惑。所以分享给大家我看的面向对象网课里的一个例子,保准一看就懂!

  class son{
       public:

       ~son()  {cout < <"bye from son"< <endl; }
  };

  class grandson :public son {
        public:

     ~grandson() {cout< <"bye from grandson"<<endl; }
};

  int main () [

  son *pson;

  pson=new grandson() ;
  delete pson;
  return 0;

}
输出:bye from son           没有执行  grandson::~grandson() !!!

解决办法:

class son{
       public:
       virtaul ~son()  {cout < <"bye from son"< <endl; }
};
 //  只需要把基类的析构函数声明为虚函数就OK了。
~~~~~其他的不变
输出结果:
bye from grandson
bye from son
    从输出结果看,你就知道函数被调用的先后了:
     执行:  grandson::~grandson() , 引起执行: son::~son()   !  !  !
   趁热打铁,再介绍虚函数世界里的另一个奇葩:纯虚函数  
 还有一个名词:抽象类

纯虚函数:没有函数体的虚函数。
既然不存在函数体,那你也就不需要再类外定义函数体了。
纯虚函数也可以有成员变量和成员函数,不是只有纯虚函数。

class A{
        pivate:
            int a;
        public:
            virtual void A_B_C()  =  0 ;  //  “ = 0 ” , 纯虚函数 !!! 
            void D_E_F() {}  //   函数体为空,但不是纯虚函数
};

有人也许纳闷了,函数体都没有,那纯虚函数有什么用?
它是有用的,在说之前先把一些概念说完。

!包含 纯虚函数 的类叫 抽象类。

抽象类与一般类的区别:

(1)抽象类“只能”作为 / 基类 / 来 / 派生新类使用,不能“单独”创建抽象类的独立对象。
而抽象类的对象也存在,但它一定被包含在一个派生类对象里。

抽象类的指针和引用可以指向由抽象类派生出来的类的对象.。

A a;  //错,A是抽象类,不能创建对象
A *pa;  //ok,可以定义抽象类的指针和引用
pa = new A;   //错误,A是抽象类,不能创建对象
      以下是要注意的重点,认真记好,不然小心练功走火入魔!!!(编译出错)

!!!在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部
不能调用纯虚函数。

 解释:
 在成员函数内部调用虚函数是多态,而在构造函数或析构函数内部调用虚函数不是多态;
 是多态就可以调用纯虚函数,不是多态就不能调用纯虚函数。

!!!如果一个类从抽象类派生而来,那么 / 当且仅当 / 它 / 实现了基类中的 / 所有 / 纯虚函
数,它才能成为非抽象类。

解释:
在派生类里写一个和基类同名同参数表的函数,并且这个函数是有函数体的,不再是纯虚函数。
而基类的所以纯虚函数都在派生类内实现了,这个派生类就不是抽象类了。

上面这几句话确实有点难懂,说不定有人看到这直接不想看跳过了,我觉得还是接着举例子吧,简单明了点。

class A {
    public :
       virtual void f() = o ;   //  纯虚函数
       void g( ) { this->f( )  ;  //ok
       }
    A() {    //  f();  //  错误
    }

};

//  calss B的对象可以包含class A的对象,但是独立的class A对象是不存在的。
//  所以---- 当程序执行到: this -> f()时,它是通过哪个类走这一步的呢?
//  结合上上句,我们可以知道:那一步是通过A的派生类实现的。而这时的f()就不是基类里的,是派生类里的f();

class B:public A{
    public :
       void f() {  cout<<"B:f() "<<endl; }

};

int main () {
  B b;
  b.g() ;
 return 0;

}
输出结果:
    B:f()

总结一下这个知识点:

在成员函数里定义纯虚函数是没有问题的,因为这样的调用语句是多态,真正被执行的语句是派生类的那个对应的虚函数,不可能是基类那个没有函数体的纯虚函数。

而如果实在函数里的构造函数或者析构函数里调用纯虚函数,编译的时候就会出错。

因为在构造(析构)函数里调用虚函数不是多态,而既然不是多态,调用的虚函数只能是基类的那个纯虚函数,但是它没有函数体,所以编译出错。

感谢各位老板坚持看到这,虽然这份博客有许多问题,漏洞,但它确实是我花费很长时间写出来的。
如果有大佬发现这份博客的错误,可以在评论区指出来,我一定会及时修改!麻烦了!!!
希望各位读者可以为这篇博客点个赞!!!它将会激励我的学习动力!!!再次感谢!
也希望我的博客对大家的学习有帮助!
再见了。
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的clz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值