c++进阶-继承01
文章目录
- C++进阶-继承
- 继承的概念及定义
- 继承的概念
- 继承的定义
- 定义格式
- 继承基类成员访问方式的变化
- 继承类模版
- 基类和派生类之间的转换
- 继承中的作用域
- 结语
欢迎来到我的博客,很高兴和大家见面,给生活加点impetus!!开启今天的编程之路!!
今天我们学习C++进阶,进一步来学习面向对象三大特性中的第二个特性
作者:٩( ‘ω’ )و260
我的专栏:C++进阶,C++初阶,数据结构初阶,题海探骊,c语言
欢迎点赞,关注!!
C++进阶-继承
在c++初阶阶段,我们了解了面向对象的三大特性,封装,继承,多态,现在再来理解封装,比如容器的封装,迭代器的封装等等,现在我们进入继承。
继承的概念及定义
继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称派生类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用,继承是类设计层次的复用。
我们来简单举例来进行说明:
我们在课设的时候经常遇到图书馆管理系统,但是图书馆可能各种各样的人都会光临,比如此时我需要记录学号,年龄,这个只是针对学生,但是针对老师,又有不同的成员变量,此时我们可以使用继承。所以来看:
此时我将共有的成员变量放入到一个类当中,此时我学生继承这个类,随后再在自己的类中添加上新的成员变量即可。
老师也是同理的。
继承的定义
定义格式
下⾯我们看到Person是基类,也称作父类。Student是派生类,也称作子类。(既叫基类/派⽣类,也叫父类/子类)。
这是基本的定义格式,其中需要添加上:
继承基类成员访问方式的变化
在基类当中,访问限定符分为三种,而在继承方式中,继承方式也分为三种,所以就有9中方式:
那我们如何来记忆这九种方式呢?
我们来看一个表格:
最后我们来介绍如何来记忆:
1:如果基类中的访问限定符是private的话,在派生类中基类的成员函数手撕不可见的。这⾥的不可见是指基类的私有成员还是被继承到了派⽣类对象中,但是语法上限制派⽣类对象不管在类里面还是类外面都不能去访问它。
2:实际上面的表格我们进行⼀下总结会发现,基类的私有成员在派⽣类都是不可见。基类的其他成员在派⽣类的访问⽅式==Min(成员在基类的访问限定符,继承方式),public>protected>private。
但是这里需要注意一个概念:基类的成员变量是私有的话,难道就一定不能访问吗?肯定是错误的。因为我可以在基类中写一个成员函数来操作这个私有成员变量,派生类继承的时候也会继承到这个成员函数,来看示例
class person {
public:int agecount()//基类中的成员函数能够访问基类中的私有{return ++_age;}
private:int _age = 18;//年龄
};
class student :public person
{
public:string _name;
};
int main()
{student s;cout << s.agecount() << endl;return 0;
}
来看结果:
此时我仍然对私有成员进行了访问。
3:使⽤关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
因为class如果不写访问限定符默认就是私有,struct如果不写访问限定符默认就是公有
4:基类private成员在派⽣类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派⽣类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的
在类和对象中,我们认为private和protected是相同的,都是私有,而public被定义成公有,而private和protected是在继承这里才体现出差别的。
5:在实际运用中⼀般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派⽣类的类里面使用,实际中扩展维护性不强。
实际上,我们使用的比较多的是基类是public,继承是public和基类是protected,继承是public的这两种情况,因为我们可以发现,在这两种情况中,基类的访问限定符都是没有被修改的。
总结:其实继承基类成员访问限定符的变化方式只用记忆三种就好,一种是基类为private,派生类无论如何都不可见,一种是取min的,最后一种是运用的最多的两种。
继承类模版
在栈就讲过,栈的底层是一个容器适配器,我们给的容器适配器是vector(因为效率更高),这里我们使用继承来完成同样的操作,来看代码:
template<class T>
class stack : public std::vector<T>
{
public:void push(const T& x){// 基类是类模板时,需要指定⼀下类域,// 否则编译报错:error C3861: “push_back”: 找不到标识符// 因为stack<int>实例化时,也实例化vector<int>了// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到vector<T>::push_back(x);//push_back(x);}
};
继承类模版的时候不会讲vector实例化,只有当push_back的时候,根据实参才能够将这个vector中的T给推导出来。
总结:继承类模版时,当我们需要使用类模版中的接口(容器中已经有的,自己实现的),前面需要有一个类模版的限制,在这个类模版中去查找。
原因:C++中模板类的继承成员函数不会被自动带入派生类的作用域中进行名字查找 所以需要指定类域 。
其实在这里还有一种写法:
那为什么能够这样来写呢?
虽然说这里的this指针代表a类,但是这里的语法是代表我直接强制去找
基类和派生类之间的转换
public继承的派⽣类对象(条件)可以赋值给基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分。
来画一个图理解一下:
注意:基类对象不能赋值给派生类对象。
因为基类对象中都没有_No(看图),肯定不能够这样赋值。
基类的指针或者引用可以通过强制类型转换赋值给派⽣类的指针或者引用。但是必须是基类的指针是指向派⽣类对象时才是安全的。这⾥基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。(ps:这个我们后面类型转换章节再单独专门讲解,这里先提⼀下)
这里我们来举例验证一下:
其实这里我们又可以叫做赋值兼容转换,为什么会叫这个名字呢?
我们再来看一个代码:
我们这里事先调用了一个构造函数,随后我们再使用拷贝构造。因为是赋值,所以会创建一个临时对象,临时对象具有常性,所以前面必须加上const修饰。
但是这里我们仍然是赋值,我们这里没有使用产生临时对象,所以我们这里叫做赋值兼容转换。(因为这里类型都不同了,所以我们这里叫转换)。
继承中的作用域
这里我们先说明结论:
1.在继承体系中基类和派生类都有独立的作用域。
2.派生类和基类中有同名成员变量,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。(在派⽣类成员函数中,可以使用基类::基类成员显示访问,因为继承的时候基类的成员函数不会被引入派生类,所以一定要有类域限定)
3.需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。4.注意在实际中在继承体系里面最好不要定义同名的成员(容易造成混淆)
这里只是隐藏了,不是说就找不到了。
我们来看一个例题:
很多人第一题可能会选择重载,但是重载的条件是什么?
是同名函数不同名参数在同一个作用域,这里基类和派生类都不是在同一个作用域下,怎么可能构成重载呢
而且,前面结论也提到过,只需要函数名相同就构成隐藏。
第二题:因为基类的成员函数继承时不会引入到派生类中,此时我第二个函数没有传递参数,和派生类中的成员函数无法匹配,找不到对应匹配的函数,所以这里报错链接错误。
结语
感谢大家阅读我的博客,不足之处欢迎留言指正,最后感谢大家支持!!
夫学贵有恒,锲而不舍,金石可镂。心向暖阳,无畏风雨,以勤为径,以勇作舟,必达成功之彼岸。加油!!