目录
继承
一、继承的基础介绍
普通版网页和继承版网页的区别
语法
二、继承方式
三种继承方式
三、继承中的对象模型
四、继承中构造和析构函数
五、继承同名成员的处理方式
访问同名成员:
作用域写法:
六、继承同名静态成员的处理方式
访问同名成员:
为什么能用类名访问静态成员?
静态成员:
静态成员函数:
总结:
七、多继承语法
语法:
八、菱形继承
概念:
菱形继承问题:
解决办法:
虚继承语法:
总结:
继承
一、继承的基础介绍
继承是面向对象三大特征之一
有些类和类之间存在特殊关系,如:
我们可以发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的个性。这个时候我们就可用继承的技术减少重复代码。 继承的基本语法 如很多网站中都有公共的头部,公共的底部,公共的左侧列表,只有中心内容不同,接下来用普通写法和继承写法来实现网页(我以CSDN的网页为例)中的内容,看一下继承存在的意义以及好处
普通版网页和继承版网页的区别
普通版网页
//普通版网页 class ZhuYe { public:void head(){cout << "博客 下载 学习 社区 C知道 GitCode InsCode" << endl;}void foot(){cout << "用户名 关注 收藏" << endl;}void left(){cout << "原创 周排名 总排名 访问" << endl;} }; class Czhidao { public:void head(){cout << "博客 下载 学习 社区 C知道 GitCode InsCode" << endl;}void foot(){cout << "用户名 关注 收藏" << endl;}void context(){cout << "请输入你的问题" << endl;}void left(){cout << "原创 周排名 总排名 访问" << endl;} }; class SheQu { public:void head(){cout << "博客 下载 学习 社区 C知道 GitCode InsCode" << endl;}void foot(){cout << "用户名 关注 收藏" << endl;}void context(){cout << "与我相关 最新发布 最新回复 最热 有活动 有问题" << endl;}void left(){cout << "原创 周排名 总排名 访问" << endl;} }; void test01() {ZhuYe a;a.head();a.foot();a.left();cout << "-------------------------" << endl; Czhidao b;b.head();b.context();b.foot();b.left();cout << "-------------------------" << endl; SheQu c;c.head();c.context();c.foot();c.left(); } int main() {test01();return 0; }
其实会发现这个代码中有大量重复代码,这样子的代码是很Low的,也不符合C++是面向对象的语言的标准,而且会使得代码量加大,这个在企业开发中是一定要杜绝的
继承版网页
//继承版网页 class ZhuYe { public:void head(){cout << "博客 下载 学习 社区 C知道 GitCode InsCode" << endl;}void foot(){cout << "用户名 关注 收藏" << endl;}void left(){cout << "原创 周排名 总排名 访问" << endl;} }; class Czhidao : public ZhuYe { public:void context(){cout << "请输入你的问题" << endl;} }; class SheQu : public ZhuYe { public:void context(){cout << "与我相关 最新发布 最新回复 最热 有活动 有问题" << endl;} }; void test01() {ZhuYe a;a.head();a.foot();a.left();cout << "-------------------------" << endl; Czhidao b;b.head();b.context();b.foot();b.left();cout << "-------------------------" << endl; SheQu c;c.head();c.context();c.foot();c.left(); } int main() {test01();return 0; }
以上是继承版代码,可以看出来他将重复的部分给删掉了,这里用了继承这个语法,现在我来讲讲继承的语法
语法
语法:class 子类 : 继承方式 父类
子类:又称派生类
父类:又称基类
派生类中的成员包括两大部分 一类是从基类继承过来的,一类是自己增加的成员 从基类继承过来的表现其共性,而新增的成员则体现了个性
二、继承方式
三种继承方式
公共继承
保护继承
**私有继承
从图可知:
父类中的私有成员,不管子类以哪种方式继承,都不可访问
就像父亲的银行卡密码,就算你是他儿子,他也不会告诉你,因为那是他的私有财产
三、继承中的对象模型
问题,从父类继承过来的成员,哪些属于子类中?
答案:
1.父类所有非静态成员属性都会被子类继承下去 2.父类中的成员属性是被编译器给隐藏了,因此是访问不到的,但是确实被继承下去了,大家可以用以下代码检测一下
class A { public:int m_A; protected:int m_B; private:int m_C; }; class B : public A { public:int m_D; }; int main() {B test;cout << sizeof(test) << endl;return 0; }
以上代码的答案是:16,虽然B只能访问A的m_A,m_B,加上自己的m_D,只有12个字节数,但是父类的m_C只是不可访问,不等于其不存在,因此,可以看出来子类继承了父类中所有非静态成员
四、继承中构造和析构函数
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构函数顺序谁先谁后?
大家可以通过以下代码来看看:
class A { public:A(){cout << "父类构造函数执行" << endl;}~A(){cout << "父类析构函数函数执行" << endl;} }; class B : public A { public:B(){cout << "子类构造函数执行" << endl;}~B(){cout << "子类析构函数函数执行" << endl;} }; int main() {B test;return 0; }
会发现继承中的构造和析构顺序如下: 先构造父类,再构造子类,析构的顺序与构造的顺序相反
五、继承同名成员的处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象访问父类中同名的数据呢?
访问同名成员:
访问子类同名成员 直接访问即可
访问父类类同名成员 需要加作用域
作用域写法:
对象名.父类::成员名
大家可以看看以下代码来理解
class A { public:int m_A = 10; }; class B : public A { public:int m_A = 20; }; int main() {B test;cout << test.m_A << endl;cout << test.A::m_A << endl; return 0; }
如果子类中出现与父类同名成员函数;要访问就要加作用域
六、继承同名静态成员的处理方式
继承同名静态成员在子类对象上如何访问? 静态成员和非静态成员出现同名,处理方式一致
访问同名成员:
访问子类同名成员 直接访问即可
访问父类类同名成员 需要加作用域
不过,静态成员有两种方式访问
通过对象
通过类名
第一种方法与非静态成员一样的方式,我就不过多赘述,我来讲讲第二种方式
为什么能用类名访问静态成员?
因为静态成员与静态成员函数在内存中都只有一份,所以所有对象都能直接访问他,因此只需要类名就能知道它具体的值
类名访问语法(以以下代码的访问为例)
class A { public:static int m_A; }; int A::m_A = 10; class B : public A { public:static int m_A; }; int B::m_A = 20; int main() {cout << B::m_A << endl;cout << B::A::m_A << endl; return 0; }
插入静态成员知识点:
静态成员:
类型前加static
类内声明,类外初始化,一定要初始(因为静态变量放在全局区,全局区在编译阶段就分配内存)
静态成员函数:
返回类型前加static
只可访问静态变量
总结:
同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(对象,类名)
七、多继承语法
C++中允许一个类继承多个类
语法:
class 子类 : 继承方式 父类1,继承方式 父类2, 继承方式 父类3……
多继承可能会引发父类有同名成员出现,要加作用域区分,因为容易出错,所以C++实际开发中不建议用多继承,因此不作过多介绍
八、菱形继承
概念:
两个派生类继承同一个基类 又有某个类同时继承两个派生类
以下例子虽然不符合事实动物的来源,但是有利于理解,大家就理解概念就好
菱形继承问题:
1.羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性
class Animal { public:int m_Age; }; class Sheep:public Animal { public:int m_Age; }; class Tuo :public Animal { public:int m_Age; }; class SheepTuo :public Sheep, public Tuo { public:int m_Age; }; void test() {SheepTuo st;st.Sheep::m_Age = 20;st.Tuo::m_Age = 10; } int main() {test();return 0; }
以上代码就有二义性:羊驼的年龄应该是和羊一样为20岁,还是应该和驼一样为10岁呢?
2.羊驼继承的动物的数据继承了两份,这份数据我们只需要一份就行
解决办法:
利用虚继承,解决菱形继承的问题
虚继承语法:
在继承之前加上关键字virtual变成虚继承
class Animal { public:int m_Age; }; class Sheep:virtual public Animal { public:int m_Age; }; class Tuo :virtual public Animal { public:int m_Age; }; class SheepTuo :public Sheep, public Tuo { public:int m_Age; }; void test() {SheepTuo st;st.Sheep::m_Age = 20;st.Tuo::m_Age = 10; } int main() {test();return 0; }
这时你
cout << st.m_Age << endl; cout << st.Sheep::m_Age << endl; cout << st.Tuo::m_Age << endl;
都是等于10,因为三者在实际上时共用了一个数据,这里涉及了虚拟基类指针和虚拟基类表,这里涉及开发命令页的操作来展现,大家感兴趣的可以自行查找相关资料
总结:
菱形继承带来的主要问题是:子类继承两份相同的数据导致资源浪费以及毫无意义 可以用虚拟继承方式解决