第三十九篇,C++的继承和派生,多继承的语法特性以及虚继承的概念的引出。

1.继承和派生


==============================================
   1.什么是继承,派生
                 继承的好处,利用父辈打下的江山,继续努力奋斗,创造更好的业绩
         C++中:一个类继承另外一个类
                继承的好处,利用另外一个类已经写好的成员函数,复用代码
                            子类继承了父类,子类对象可以直接使用父类所有的成员方法(公有和保护)
                继承用来描述生活中is-a这种关系
                比如:  class Animal //动物类
                       {
                        public:
                           动物常用方法都写好了,动物跑,跳,吃,睡觉
                       }
                       class 自己写的类 继承 动物类
                       {
                             

                       }
              图形类  三角形  长方形  圆形
         派生:三角形继承了圆形类
               圆形类派生了三角形
         父类(基类): 圆形类就是父类
         子类(派生类):三角形就是子类

   2.语法规则
         class 子类名字:public     父类名字  //公有继承
         class 子类名字:private    父类名字  //私有继承
         class 子类名字:protected  父类名字  //保护继承
         {

              子类自己的内容
         };
         注意:三种继承方式,使用最多的是公有继承

   3.三种继承的区别
     第一种:公有继承 
          父类成员的权限   public        private     protected
          子类的内部        ok             no          ok
          子类的外部        ok             no          no   

#include <iostream>
using namespace std;

class Animal
{
public:
	void eat()
	{
		cout<<"动物吃"<<endl;
	}
	void sleep()
	{
		cout<<"动物睡"<<endl;
	}
};

//猫(子类/派生类)继承了Animal(父类/基类)
class Cat:public Animal
{
public:
	void study()
	{
		cout<<"我是有追求的,我爱学习"<<endl;
	}
};

class Pig:public Animal
{
	
};


int main()
{
	Cat c1;
	c1.eat();
	c1.sleep();
	c1.study();//可以使用c1使用父类的共有方法
}

===============================================================     

第二种:私有继承 
          父类成员的权限   public        private     protected
          子类的内部        ok             no          ok
          子类的外部        no             no          no    

==============================================================     

第三种:保护继承 
          父类成员的权限   public        private     protected
          子类的内部        ok             no              ok
          子类的外部        no             no              no

===============================================================

  4.子类的大小(子类地址空间是如何组成的)
         子类的大小=父类大小+子类本身大小    也要满足字节对齐(跟结构体对齐规则一模一样)
    子类对象之所以可以调用父类的公有方法,原因就在于子类的地址空间中包含父类对象的一个副本,编译器是通过这个父类对象的副本去调用父类的公有方法
         子类对象.父类的公有方法()  //表面上看是利用子类对象去调用父类的公有方法
                                     实际上编译器是利用子类对象中包含的那个父类的副本去调用的

  5.继承之后,构造函数和析构函数的调用规则
         构造函数的调用的规则
                先调用父类的无参构造函数,然后再调用子类自己的构造函数
                注意:如果父类没有无参构造函数,编译器会报错
                      无论子类对象创建的时候是否传递了参数,父类都是使用无参构造
         析构函数的调用的规则
                先析构子类,再析构父类
    子类的构造函数可以使用参数列表的形式,去指定调用父类某个版本的构造函数
         例如:
              子类构造():父类构造(45,63)
          {
        cout<<"子类无参构造函数"<<endl;
          }
          子类构造(int n):父类构造(n)
          {
        cout<<"子类有参构造函数,参数是: "<<n<<endl;
          }

  6.子类出现了跟父类同名的函数
        子类对象.函数名            //默认调用子类自己的同名函数
        子类对象.父类名::函数名;   //指定调用父类的同名函数


2.多继承


==============================================
   1.概念
         单继承:子类继承了一个父类
         多继承:子类继承了多个父类,描述生活中某个事物同时具备多个类的属性
                 豹子--》哺乳动物
                         猫科动物
                 圆桌--》圆形
                         桌子

   2.语法规则
         class 子类名:public 父类1,public 父类2,......   //如果还有其它的父类,继续往后写
         {

         }     


==============================================================

调用父类构造的顺序只与继承父类的顺序有关,与调用时写的函数顺序无关。

​
#include <iostream>

using namespace std;
class Circle //圆形类
{
public:
	Circle()
	{
		cout<<"Circle无参构造"<<endl;
	}
	Circle(string str)
	{
		cout<<"Circle一个参数的构造"<<endl;
	}

};

class Desk //桌子类
{
public:
	Desk()
	{
		cout<<"Desk无参构造"<<endl;
	}
	Desk(int n)
	{
		cout<<"Desk一个参数的构造"<<endl;
	}
};

class RoundTable:public Desk,public Circle //圆桌类
{
public:
	//RoundTable():Desk(666),Circle("圆形")//两个指定
	//RoundTable():Desk(666)
	//RoundTable():Circle("圆形")
	RoundTable():Circle("圆形"),Desk(666)
	{
		cout<<"子类无参构造"<<endl;
	}
};

int main()
{
	RoundTable r;
}

​

 =================================================================   

 
   3.多继承需要注意的点


         第一个:父类有多个,必须严格按照从左到右的顺序去看父类的调用顺序(计算子类大小也是要按照这个顺序去计算)。

=======================================================

#include <iostream>

using namespace std;
class Circle //圆形类
{
public:
	void getarea()
	{
		cout<<"圆形求面积"<<endl;
	}
private:
	int a;
};

class Desk //桌子类
{
public:
	void fun()
	{
		cout<<"桌子用来吃饭,写作业"<<endl;
	}
private:
	double b;
};

class RoundTable:public Desk,public Circle //圆桌类
{

private:
	short c;
};

int main()
{
	cout<<"子类roundtable大小是: "<<sizeof(RoundTable)<<endl;     //24
}

========================================================

   4.多继承的特殊情况(虚继承)


         特殊情况:环状继承 
                   类A派生出类B,类C
                   类B,类C共同派生出类D
                           动物类A
                       哺乳B      猫科C
                            豹子D
         直接父类:
                 A是B的直接父类
                 B是D的直接父类
         间接父类:
                 A是D的间接父类


    环状继承引发的问题:
    问题一:创建子类D的对象,会导致A构建多次,浪费存储空间(理想情况是我只希望A构建一次)

创建豹子对象的时候,会根据继承的顺序class Leopard:public Mammal,public Catamount,会先调用Animal的无参构造,再调用Mammal的无参构造;然后调用Animal的无参构造,再调用Catamount的无参构造,最后子类构造。 Animal构建多次,浪费存储空间

#include <iostream>
using namespace std;

class Animal
{
public:
	Animal()
	{
		cout<<"Animal无参构造"<<endl;
	}
private:
	int a;//添加一个私有成员
};

class Mammal:public Animal  //哺乳动物
{
public:
	Mammal()
	{
		cout<<"Mammal无参构造"<<endl;
	}
private:
	double b;
};

class Catamount:public Animal //猫科动物
{
public:
	Catamount()
	{
		cout<<"Catamount无参构造"<<endl;
	}
};


class Leopard:public Mammal,public Catamount
{
public:
	Leopard()
	{
		cout<<"Leopard无参构造"<<endl;
	}
};

int main()
{
	//创建豹子的对象
	Leopard l;
	
	cout<<"豹子大小: "<<sizeof(Leopard)<<endl;//8
}

添加一个私有成员,查看豹子类的大小。Animal副本赋值了两份,浪费了地址空间。

======================================================================

  问题二:子类D的对象,调用A里面的方法,会引发二义性
  二义性:编译器搞不清楚,你究竟是想要用B创建的A副本来调用,还是想要用C创建的A副本来调用

#include <iostream>

using namespace std;

class Animal
{
public:
	Animal()
	{
		cout<<"Animal无参构造"<<endl;
	}
	void show()
	{
		cout<<"Animal提供的show方法"<<endl;
	}
};

class Mammal:public Animal  //哺乳动物
{
public:
	Mammal()
	{
		cout<<"Mammal无参构造"<<endl;
	}
};

class Catamount:public Animal //猫科动物
{
public:
	Catamount()
	{
		cout<<"Catamount无参构造"<<endl;
	}
};


class Leopard:public Mammal,public Catamount
{
public:
	Leopard()
	{
		cout<<"Leopard无参构造"<<endl;
	}
};

int main()
{
	//创建豹子的对象
	Leopard l;
	//我要调用Animal的show方法
	
	//错误的,有二义性
	//l.show();  //本质上是通过子类里面包含的父类的副本去调用show
	
	//解决方法
	l.Mammal::show();     //我想通过Mammal创建的Animal副本调用show方法
	l.Catamount::show();  //我想通过Catamount创建的Animal副本调用show方法
	
}

==============================================================

    5.虚继承解决刚才环状继承出现的两个问题
虚继承的语法格式:
                子类:virtual public 父类  //虚继承
                {


                }   


     
     虚继承的底层原理
                一个类虚继承了另外一个类,那么在这个类的地址空间中会多出一个指针,该指针用来指向虚基类表的首地址
         虚基类:         被继承的那个类就是虚基类
         虚基类表:   C++中开辟一块内存区域,专门用来存放所有的虚基类首地址的
                              用来存放虚基类首地址的一种数据结构  

虚继承的底层原理图示:

===================================================================

一个类虚继承了另一个类,这个类的地址空间中会多出一个指针。代码如下:

#include <iostream>

using namespace std;

class Animal
{
public:
	int a;
};

class Mammal:virtual public Animal  //哺乳动物
{
 
};

class Catamount:virtual public Animal //猫科动物
{

};


class Leopard:public Mammal,public Catamount
{

};

int main()
{
	cout<<"动物大小是:  "<<sizeof(Animal)<<endl;
	cout<<"哺乳动物大小是:  "<<sizeof(Mammal)<<endl;  //虚继承,会多出一个指针,大小为16
	cout<<"猫科动物大小是:  "<<sizeof(Catamount)<<endl;  //虚继承,会多出一个指针,大小为16
}

================================================================

验证虚函数的底层原理

要求你封装一个函数,该函数可以展示各种动物吃什么
问题一:如何让参数具有通用性?(各种动物都能匹配)
               参数写成父类的指针/引用
问题二:用虚函数解决了调用不同子类的同名方法        
 

#include <iostream>

using namespace std;

class Animal
{
public:
	virtual void eat()
	{
		cout<<"动物吃"<<endl;
	}
	int a;
};

class Cat:public Animal
{
public:
	void eat()
	{
		cout<<"猫吃鱼"<<endl;
	}
};

class Dog:public Animal
{
public:
	void eat()
	{
		cout<<"狗吃骨头"<<endl;
	}	
};

class Bear:public Animal
{
public:
	void eat()
	{
		cout<<"熊吃光头强"<<endl;
	}
};

//void showanimaleat(参数的类型让你很为难)
//void showanimaleat(Cat &other)  //不好,有局限性,只能传递猫的对象引用
//void showanimaleat(Animal &other) //很好,具有通用性,猫狗熊都继承了Animal
void showanimaleat(Animal *other)  //很好,具有通用性,猫狗熊都继承了Animal
{
	other->eat();
}


int main()
{
	cout<<"Animal大小是: "<<sizeof(Animal)<<endl;//4
	cout<<"Cat大小是: "<<sizeof(Cat)<<endl;//
	cout<<"Dog大小是: "<<sizeof(Dog)<<endl;//4
	cout<<"Bear大小是: "<<sizeof(Bear)<<endl;//4
}


 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

肖爱Kun

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

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

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

打赏作者

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

抵扣说明:

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

余额充值