[C++] 面向对象语言的三大特性--继承
Posted 一个正直的男孩
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C++] 面向对象语言的三大特性--继承相关的知识,希望对你有一定的参考价值。
文章目录
什么是面选对象语言的三大特性?
这里先简单来说下什么是面向对象的三大特性
- 封装
- 继承
- 多态
封装
把函数和变量全部整合在一起
进行管理,保护
,如兵马俑如果不造围墙保护起来,限定你的活动范围起来,不然这些文物没几年说不定就消逝了。而封装也是一样把重要的数据给保护起来,暴露我想给接口访问
什么是继承?
继承这个词一般是在那个场景会用到呢?意志
那我们来以这个例子来引出继承吧
void test1()
int a =1;
int b =2;
//交换
int c=a;
a=b;
b=c;
void test2()
int a=2;
int b=4;
//交换
int c= a;
a=b;
b=c;
上面的代码你会发现很冗余交换这一部分的代码写了俩次,那么如果是你会如何做呢?
如果是我的话我会吧这一部分的代码提出去封装一个函数
,那么我调用这个函数就可以理解为继承
那么下面这种情况要如何处理呢?在自定义类型(class,struct……)中出现了冗余
class Student
public:
int StuId;
//重复部分
string name;
string address;
int telephone;
;
class Teacher
public:
int TechId;
//重复部分
string name;
string address;
int telephone;
;
那我们也可以和向函数一样吧共的部分给提出来封装一个类,那么这个时候有俩种方法可以使用
- 在本类中创建一个重复部分类的对象
- 继承
在这里下一个定义其实也是人如其名,他可以继承下 父亲类(当前例子封装的类)的 ’‘全部’‘ 成员
方法1
class PersonMessage
public:
string name;
string address;
int telephone;
;
class Student
PersonMessage d1;
int StuId;
;
class Teacher
PersonMessage d1;
int TechId;
;
方法2
class PersonMessage
public:
string name;
string address;
int telephone;
;
class Student: public PersonMessage
int StuId;
;
class Teacher:public PersonMessage
int TechId;
;
第一种叫方法叫做 “黑盒”,第二种叫做“白盒”(文章末尾在进行解析)
继承的使用说明
在类名后 加 : 继承方式 被继承的类
当俩个俩处于继承关系的时候他们他们就有一个专有的叫法
-
被继承的类叫做
父类或者基类
-
继承的类叫做
子类或者派生类
继承方式:
这个是用来确定继承下来的数据的属性
(public,private,protected)其中之一,且继承下来的属性还受父类限定符
的影响
那么简单的看看有哪些继承方式呢?
protected英语时保护的意思,没有继承的情况下和private一致,但继承后和private不同了,这就是代替了原来的private限制符,
可以继承但是是保护(类外部无法直接访问)
那么上面不仅仅是由继方式还和父类的限定符有关那如何确定呢?
仔细观察上面的表
public继承public成员还是public
public继承protected就成了protectoe
private继承,public与protected都是private
可以看到是一个权限缩小的感觉,那么可以得出这样的关系public>protected>private
,而继承是继承权限小的属性
不可见是一个啥意思呢?
可以继承,但在
派生类中是不可使用的
。就如没有继承的时候你访问类的时候只可以访问公有的成员。private是不可用访问的这里也是类似
特殊用法,子类给父类赋值
你是否会疑问??? 自定义类型不是同类型的才支持赋值吗?
简单理解他可以类似
不同内置类型赋值,他底层会强制类型转换
,而子类赋值给父类的“强制类型转换”叫做切片
切片的原理
就是把子类继承父类中的变量给切割出来如图所示:
你或许会疑问??继承下来的为啥都在聚集在一起且在上半部分
这个与构造有关系本文下面有专门的讲解
子类可以赋值给父类,那么父类的指针与引用可不可以赋值呢?
那当然是可以的,和子类赋值父类一样,先切片,然后指针和引用就指向切下来的那块区域
class father
public:
string name;
string address;
int telephone;
int age;
char sex;
;
class child:public father
public:
string favourite;
;
int main()
father d1;
child d2;
…………(d2赋值操作)
d1 =d2;//对象
father *d3=&d2;//指针
father& d4=d2;//引用
如图所示:
继承后构造、析构…………是如何实现的?
是否有这样的疑问,父类的元素如何初始化、拷贝、析构,赋值,是如何实现的?
构造
这里相比之前的构造不同需要分为三类
- 内置类型不处理
- 自定义类型调用
- 父类(直接看成一个
整体,自定义类型
处理)
情况1(不写构造函数):
父类调用自己的构造函数进行初始
情况2(写构造函数)
上述说过父类是看做一个整体,一个自定义类型,那么我们就可以单独写一个构造函数,指定构造父类的成员(类似STL实现多种构造进行重载)
如上所说父类是一个整体,一个自定义类型,只是没有变量名,所以
直接用类名直接调用构造函数
数据构造的顺序
是父类先调用构造函数还是子类先调用?
linux下的结果
可以看到父类的变量地址比较低,且他们是内置类型,他们是存在栈上的,那么栈满足就是先
定义先压栈,且占是向低地址增长的
。以linux为主吧,父类先初始化
,子类后,clion编译器下是相反的
拷贝构造,赋值重载
和构造函数一样,他也是被分为三种情况,具体情况如上
情况1(不写写拷贝构造与赋值重载):
情况2(写拷贝构造与赋值重载):
这里拷贝构造与构造函数一样,但是赋值重载则有大问题
如图所示:
解释:
可能一些小伙伴马上就反应出来了,局部优先,半对半错吧。
这一块其实是一个隐藏
,当父类和子类中有变量的变量名相同
时就会构成隐藏
,简单理解就是局部优先
那么如何解决呢?
这一块现在对你来说应该很简单吧,只需要制定一下类域即可
有人可能会问,父类可以用子类拷贝构造吗?可以给我赋值吗?
验证:
解释:
这里为什么会成功,应为运用了上述的一个操作
“切片”
,把子类中继承下来的数据切割出来,然后进行拷贝构造或者赋值重载
析构
这里也是被分为三种情况,具体情况如上
代码:
解释:
这里的意思大致说这个代码没有呀意义,因为这里
构成了隐藏
,为啥呢?不是要父子类函数名相同才构成重载吗,我不是函数名不同吗,这里编译器底层吧析全部替换成了distract
(后面多态会用下篇博客)
解决方案
既然是隐藏那么我指定类域
这里你会看到一个特殊的情况,为啥我析构了俩次
解释:
上面有个概念就是
构造的顺序
,父类先构造,然后子类,按照栈的结构
来看,会先析构子类,然后在析构父类。上面的代码我们指定析构父类
,然后子类的析构完就调用父类的析构
,才导致了double次析构
继承有啥缺陷吗?
看着继承似乎很完美,但是他有一个致命的缺陷,以至于这个继承在语言中只有C++有这一特性,java已经舍弃,那么他的缺陷就是多继承导致的菱形继承
菱形继承
当多继承的时候会出现这种情况,就是俩个父类都继承了同一个类
那么就会造成一个问题就是数据冗余
(继承了俩份一样的数据),且有二义性
(如图,class4 访问class中的数据时不知道访问class2 还是 class3 )
解决方案
- 指定类域访问(解决不了冗余)
- 虚继承
代码:
class PersonMessag//对应上图class 1
public:
string name;
string address;
int telephone;
;
class Student: public PersonMessage//对应上图class 2
public:
int StuId;
;
class Teacher :public PersonMessage//对应上图class 3
public:
PersonMessage d1;
int TechId;
;
class Graduate:public Student,Teacher//对应上图class 4
;
多继承的缺陷:
int main()
Graduate person;
person.name;//error
如图所示:
解决方法一(指定类域)
int main()
Graduate person;
person.Student::name;
person.Teacher::name;
有没有感觉似曾相识,这个场景在哪里见过?
命名空间,本质都是一样的
解决方法二(虚继承)
在重复继承的方式前加一个关键字 virtual
(上图中 class 3 与 class 4 处)
代码:
class PersonMessage
public:
string name;
string address;
int telephone;
;
class Student:virtual public PersonMessage//腰处1,class 3
public:
int StuId;
;
class Teacher :virtual public PersonMessage//腰处1,class 4
public:
int TechId;
;
class Graduate: public Student,Teacher
;
对象内存模型视图
偏移量指针指向一张‘’表‘’,
虚基表
,里面存有俩个元素一个是关于多态(下篇博客讲解),还有就是偏移量(上图中Tehcer,Studen 到 PersonMessage的距离)
面试看能会问到的题目
父类与子类的作用域
当父类与子类有重名的变量时那么下面代码运行结果时啥?
class Person
protected :
int _num = 111; // 身份证号
;
class Student : public Person
public:
void Print()
cout<<" 学号:"<<_num<<endl;
protected:
int _num = 999; // 学号
;
int main()
Student s1;
s1.Print();
结果:
解释:
抽象来理解就是局部优先,有同名的变量时先执行本类的。
真正的定义:
上述这些操作有一个专有名词就是隐藏
,当子类和父类有变量名相同,那么子类会自动隐藏父类的变量
当父类与子类有重名的函数时那么下面代码fun算是重载还是隐藏
class A
public:
void fun()
cout << "func()" << endl;
;
class B : public A
public:
void fun(int i)
A::fun();
cout << "func(int i)->" <<i<<endl;
;
int main()
B b;
b.fun(20);
解释:
这一块稍微点概念疏忽这一题就会选重载,重载时要在同一作用域下才会进行重载,而从题目1,2 可以看出,
父类与子类是有各自的作用域的
,所以不会构成重载,而是隐藏
下面代码中有多少个静态成员_count?
class Person
public :
Person ()
++ _count ;
static int _count; // 统计人的个数。
protected :
string _name ; // 姓名 public :
;
int Person :: _count = 0;//静态成员
class Student : public Person
;
class Graduate : public Student
;
void TestPerson()
Student s1 ;
Student s2 ;
Student s3 ;
Graduate s4 ;
cout<<Person::_count<<endl;
//验证
Student ::_count = 0;
cout<<Person::_count<<endl;
结果:
解释:
在继承流
全部类共享这个静态变量
,所以修改就是修改同一个静态变量
补充知识(白盒与黑盒)
白盒就可以理解里面的数据都是可见,而黑盒是不可见的(直观),如图所示:
其实更精确的来说就是白盒看起来就是属于
自己本类的
,黑盒就是像一个哆啦A梦的口袋
一样看不到
,到那时有需求去拿就好
(大雄~~~)
那么现在有这样一个问题是白盒好还是黑盒好?
算是写程序的一个宗旨就是
“高内聚,低耦合”
,模块与模块直接是低耦合(模块与模块关联小),但是模块本身是高内聚(模块自身关联强),举个例子:
三人要用积木搭个机器人,一人搭头,一人搭身体,一人搭手脚,假设搭好组装后发现不匹配,那么另外俩人就要重搭或者改造(这就是抽象的模块与模块之间高内聚,就会导致一个模块出错,别的也出错)
,如果是一个人之间搭身体,俩个人搭武器,如果机器人需改改动,这武器只需要微小的改动(模块与模块之间的低耦合,一个模块出错,对另外模块影响较小)
唠唠家常
这篇博客多多少少写了快半个月了,期间经过了过年,每天有一搭没一搭的写一点或者就不写,没错年已经过去7天了。
以上是关于[C++] 面向对象语言的三大特性--继承的主要内容,如果未能解决你的问题,请参考以下文章