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
}