类的特性




/*******************************类的继承*******************************/

class Animal
{
 public:
  void eat()
  {
   std::cout << "Animal eat" << std::endl;
  }
 protected:
  void sleep()
  {
   std::cout << "Animal sleep" << std::endl;
  }
 private:
  void fly()
  {
   std::cout << "Animal fly" << std::endl;
  }
};

class Fish : public Animal
{
 public:
  void swim()
  {
   std::cout << "Fish swim" << std::endl;
  }
};

Animal为父类(基类) Fish为子类(派生类);
这是最简单的公有继承
对于外界 只有public的内容可以被访问
对于自己 自己的所有内容可被访问
对于子类 子类可以访问自己的所有内容 而且可以访问父类的public:内容 protected:内容

/*******************************类的封装*******************************/

对于继承的方式和权限:

类的继承特性:    父类的访问特性:     子类的访问特性:
       public:       public:
public:     protected:      protected:
       private:       No access:
------------------------------------------------------------
       public:       protected:
protected:    protected:      protected:
       private:       No access:
------------------------------------------------------------
       public:       private:
private:     protected:      private:
       private:       No access:
------------------------------------------------------------


/*******************************类的多态*******************************/

class Animal
{
 public:
  Animal()
  {
   std::cout << "Animal Construct" << std::endl;
  }
  ~Animal()
  {
   std::cout << "Animal Destruct" << std::endl;
  }
};

class Fish : public Animal
{
 public:
  Fish()
  {
   std::cout << "Fish Construct" << std::endl;
  }
  ~Fish()
  {
   std::cout << "Fish Destruct" << std::endl;
  }
};


构造函数与析构函数:
首先 我们实例化一个Fish fi;
会首先调用父类Animal的构造函数; 然后调用子类Fish的构造函数;
然后析构的时候 会先调用子类的析构函数, 在调用父类的析构函数
运行结果:
Animal Construct
Fish Construct
Fish Destruct
Animal Destruct

/------------------------------------------------------------------------/

如果此时 我们把Animal的构造函数改为Animal( int a, int b );
那么Fish类在构造的时候 就无法调用到父类Animal的构造函数 会报一个错 无合适的构造函数
因为Animal类已经没有默认构造函数了, 所以Fish类无法先构造Animal;
现在解决办法 Fish类的构造函数 改为  Fish() : Animal( 200, 300 )
现在是传递了常量初始化 可以改为 Fish( int a, int b ) : Animal( a, b )
这就是在构造函数之后初始化; 冒号:后 可以初始化 用逗号连接;

/------------------------------------------------------------------------/

如果此时 在Animal类中 写了一个 void Breath();函数
在Fish类中 也写了一个 void Breath();函数 他们返回值 函数名 参数列都一样;
这样就会产生函数的"覆盖"; 当我们继承了一个类的时候 我们可以覆盖一些行为;
此时 对于父类 他还是会调用"自己"的 Breath();函数, 对于Fish子类 他会调用属于"自己"的 Breath();函数

此时 也可以使用作用域解析符号 让子类又使用自己的方法 又使用父类的方法
void Breath()
{
 Animal::Breath();
 std::cout << "Fish" << std::endl;
}

/------------------------------------------------------------------------/

假设有如下两个类:
class Animal
{
 public:
  void Func1();
};

class Fish : public Animal
{
 public:
  void Func2();
  void Func3();
};

int main( int argc, char *argv[] )
{
 Animal an;
 Fish fi;
 return 0;
}

--> 第一类:
最简单的 an可以调用Func1(); fi可以调用Func2(); Fun3();

--> 第二类:
如果设置 an = fi; 结论是: an可以调用Func1(); fi可以调用Func2(); Func3();
先讨论一下内存模型: 对于一个animal类成员an 他的内存模型就是一片连续的内存
而对于一个Fish类成员fi; 他先构造了Animal类对象在构造了自己; 所以他的内存模型是 Animal类内存模型+Fish类内存模型
然后 an = fi; 相当于发生了一个截断, 最后an还是an; 所以并没有实际意义;

-->第三类:
如果设置 fi = an; 这种类型即为小进大 但是也不允许, 因为这是一个类对象 类对象的生成需要进行构造
但是等号并无法进行Fish类内存模型构造 所以这种方式不允许;

/------------------------------------------------------------------------/

假设有如下两个类:
class Animal
{
 public:
  void Func1();
};

class Fish : public Animal
{
 public:
  void Func1();
  void Func2();
};

int main( int argc, char *argv[] )
{
 Animal *an;
 Fish *fi;
 return 0;
}


--> 第一类:
最简单的 an可以调用Func1(); fi可以调用Func1(); Fun1(); (用的都是自己的)

--> 第二类:
如果设置 an = fi; 结论是: an可以调用Func1(); fi可以调用Func1(); Func2();
给一个解释: 首先 对于父类和子类的内存结构 在上面 对于指针类型的赋值
只是把指针4字节的内容强制改变了数据, 至于他指向了什么 这不一定( 或者说这是需要我们来保证的 )
之后 编译器会"按照接收地址的内存模型来解读这个地址"
( 类似如上就是: 把fi的地址给了an; 然后编译器按照an的内存模型来解读了fi )
在这里就会发现: 其实还是一个截断 an的内存模型只有父类部分 所以对an的解读也只有父类部分
所以an可以调用Func1();

-->第三类:
如果设置 fi = an; 这个是不行的 原因同上 an是小 fi是大 小进大 缺的部分无法构造

/------------------------------------------------------------------------/

class Animal
{
 public:
  virtual void Func1();
};

class Fish : public Animal
{
 public:
  virtual void Func1();
  void Func2();
};

int main( int argc, char *argv[] )
{
 Animal *an;
 Fish *fi;
 return 0;
}

--> 第一类:
最简单的 an可以调用Func1(); fi可以调用Func1(); Fun1(); (用的都是自己的)

--> 第二类:
如果设置 an = fi; 结论是: an可以调用Func1(); fi可以调用Func1(); Func2();
如上两种情况所说 其实给进行 an = fi; 其实没啥大用, 因为他们都调用了自己的函数;
但是有一种情况我们是需要解决的 我们希望使用父类的指针来模拟子类的行为
或者说 我们希望使用一根父类的指针 来调用我们在子类里"覆盖"了的函数
那么我们需要使用 "虚函数"; 
这样我们就可以使用一根父类的an指针 然后来模拟在子类中被 "override" 的函数 Func1();
这就是多态, 在未引入"虚函数"之前 父类总会根据内存模型来调用父类的函数;
引入了"虚函数"之后 父类就会(在运行时采用 "迟绑定")去检查 是否有子类override了父类的virtual函数;
然后确定 调用的是哪一个函数 ( 子类的? 父类的?  "子类优先" );

 "多态的实现":
 当编译器发现一个类中有虚函数时;就会为这个类自动生成一个"虚表" ; 该表是一个一维数组,在这个数组中存放每个虚函数的地址
 而且会生成一个"指向虚表指针"; 那么对于父类Animal有一个"虚表", 子类Fish由于一定有虚函数所以也有一个"虚表"
 现在在来结合上面的信息; "编译器按照an的内存结构来解释fi"; 这里就发生了"两个动作": ( an小fi大 才会截断 大给小 ok 小给大 GG )
 第一个是"内存的截断": 这就解释了为什么fi自己的函数 这里用不了了; 因为内存截断了,( 对于an, fi自己的子类部分成垃圾了 )
 第二个是"虚表指针改变": 一开始an的虚表指针应该指向着自己的虚函数, fi的虚表指针也指向了自己的虚函数;
 而此时an指向了fi子类对象 当按照an的内存结构解释fi时 解释到的是fi的"虚表"; 所以调用到了fi的虚函数;
 
 ( 在这里在说一下 int 和 short 之间为什么可以互相等, 但是父类子类不可以互相等;
 在这里 无非就是两种情况 "大进小" "小进大";
 对于基本类型来说 "小进大" 是 "赋值" ; "大进小" 是 "截断";
 对于类来说 "小进大" 没法解释, 报错 ; "大进小" 是 "截断"; ( 这个截断和上面的不太一样, 他截断之后 自己还是自己 );
 对于类的指针来说 "小进大" 同样没法解释; "大进小" 按照 "小的方式解释"; )
 
 
-->第三类:
如果设置 fi = an; 这个是不行的 原因同上 an是小 fi是大 小进大 缺的部分无法构造

/-----------------------------------------------------------------------------/

"虚析构函数":
与上同理 如果使用了父类指针来使用子类override掉的函数,那么会存在一个问题 谁来析构
正常情况下 在"迟绑定"之后 这一根父类的指针会优先指向了子类override后的函数 这没有问题;
那么析构函数呢 对于一个类来说他必然有析构函数 而且无参数,
-->第一种情况:
{
 Fish fi;
 Animal *an = &fi;
}
当主函数中出现这种情况的时候, 不会出现任何问题
an是一根指针 出栈即完蛋, fi是个类对象 出栈会自动调用Fish的析构函数

-->第二种情况:
Fish *fi = new Fish();
Animal *an = fi;
delete an;
当我们希望通过使用一根父类的指针来delete一个子类的对象的时候, 那么就会出现问题;
这根an指针会按照父类an的内存规则来匹配 最后匹配到了父类的析构函数, 所以他会调用到父类的析构函数;
但这是不对的 因为他其实并没有把这个子类对象完全析构 子类对象剩下了一部分继承出来的 会出现意外情况;
所以我们需要"把父类的析构函数也声明为虚析构函数", 当我们检测到需要调用父类的析构函数时 他是一个"虚析构函数"
那么类就会去"迟绑定"他"真正需要的析构函数" 对于一个子类 他真正需要的析构函数是 "子类的析构 + 父类的析构"
在这里就是子类Fish的析构函数 之后在调用自己的析构函数, 这个子类对象就被完全析构了. 这就是虚析构函数存在的意义

/-----------------------------------------------------------------------------/

总结: "虚函数用于想使用父类指针来对子类对象进行某种操作的时候"

/-----------------------------------------------------------------------------/

对于一个类中 有一个函数:
virtual Func1() = 0; 
他是一个纯虚函数, 含有这个函数的类 是一个"抽象类" 他不能实例化对象;
( 对于子类,如果override了这个函数并且实现了他,才可以实例化,否则子类也不能实例化 )




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值