看懂继承|继承入门
Posted 是瑶瑶子啦
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了看懂继承|继承入门相关的知识,希望对你有一定的参考价值。
- 作者:努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:算法、数据结构、Java等相关知识。
- 博主主页: @是瑶瑶子啦
- 所属专栏: Java岛冒险记【从小白到大佬之路】;该专栏专注于Java相关知识,持续更新,每一篇内容优质,浅显易懂,不失深度!
- 近期目标:写好专栏的每一篇文章
前言
在前面的文章中,对什么是类,什么是对象已经有清晰的理解了(【JavaSE】保姆级教程|1万字+10张图入门到学会类与对象(建议收藏))。我们脑海里目前有一下几个印象:
- 类是由现实生活的事物抽象而来
- 类更多是表示一种自定义类型(记住&体会这点,在后面讲到接口的时候还会提到这句话)
- 类之间有一定的关系
- 相对独立
- 依赖(uses-a)
但是现实生活中,事物之间(类型之间)的关系并不是这么单纯。比如:动物、小狗、小猫、兔兔…动物类和其他这些类之间存在关系是–(is-a)
Java中将这种类与类之间的包含/分类关系描述为继承–is-a
这篇文章带大家入门Java第二大重要特性
文章目录
📍Part1:继承的介绍&语法
Java中是以什么样的语法来表示类与类之间这种继承关系呢?
首先要明白两个概念:
- 父类/基类
- 子类/派生类
父类和子类的关系是:子类 is-a 父类
.即子类是包含于父类的。
下面就以“前言”中提到的动物例子举例:
【重点】子类在类声明(即类名后面)加上extends 父类类名
,表示继承关系
"extends"的英文意思是“扩展”,就是指,子类继承了父类(继承父类非private属性&方法),自己也可以定义属性&方法&重写方法。本质上子类的确可以看作是父类的一种
扩展
- 父类:class Animal
public class Animal
private int age;
private String name;
public void eat()
System.out.println("吃东西ing");
//下面是两个属性的构造器
public int getAge()
return age;
public void setAge(int age)
this.age = age;
public String getName()
return name;
public void setName(String name)
this.name = name;
- 子类1:汪星人:class Dog
public class Dog extends Animal
@Override
public void eat()
System.out.println("啃骨头ing");
- 子类2:喵星人:class Cat
public class Cat extends Animal
@Override
public void eat()
System.out.println("吃小鱼儿ing");
public void climb()
System.out.println("猫咪爬树");
【注意】
- Java是
单继承
机制,即:每个类有且仅有一个父类。(C++是多继承) - 继承可以是多层的,即:在没有
final
限制的情况下,子类可以向下派生子类:
📍Part2:继承的特性
通过上面,可能还是有点懵。难道说继承的存在,仅仅是表示类之间的关系?不。
我们来深入学习一些,子类继承了父类,给我们带来了什么,以及我们怎么样去使用继承。
继承时父类和子类产生关联,带来主要以下特性:
-
子类继承父类非private属性、方法(
non-static
和static
均继承过来了) -
子类可以重写从父类继承过来的实例方法(静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能够被重写!)
-
父类引用可以指向子类对象【多态】
也正是这几点,也带来了继承的好处:
-
代码复用性提高
把公共的属性、方法定义在父类中,子类由于继承(只能继承非private修饰)了父类的属性、方法,无需重复声明&定义,提高代码复用性。子类只需要关心自己的特有属性和方法(单独声明定义of重写父类方法) -
更易于管理不同的对象
是基于多态
&动态绑定
的,(我们下文中细讲)
当然,经过上文,我们对继承有了一个大概的初印象。但是继承的使用和一些细节还有很多学问。咱们接下来打起精神。逐个攻破!
📍Part3:继承细节
📌3.1:super关键字
在【Java】还不懂this关键字?一分钟彻底弄懂this关键字我们对this关键字有了一个很透彻的认识:this关键字是对象的隐藏属性,代表此对象的引用,通过this.属性
和this.方法
我们指定可以调用本类的属性和方法。
与this关键字作用很相似的一个关键字是super
:指向当前对象的父类。
和this用法类似,只不过super是**在本类中访问父类的属性/方法/构造器:super.父类属性
/super.父类方法
/super()
(不可调用父类中被private修饰的属性or方法!)
【super作用】:
- 在子类构造器中使用
super()
来调用父类构造器,来完成对父类属性的初始化。 - 区分子类中定义的和父类中同名的属性/方法
【对比】
区别点 | this | super |
---|---|---|
本质 | this的本质是一个实实在在存在的参数,在对象调用实例方法时,会被作为隐藏参数传入实例方法中。 | super的本质仅仅就是关键字 ,编译后会被翻译成一条指令:invokespecial ,用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。 |
this/super.属性 (访问属性) | 访问本类的属性。若本类没有,逐级向上查找 | 访问其直接父类的属性,若没有,逐级向上查找 |
this/super.方法 | 同访问属性的查找规则规则 | 同访问属性的查找规则 |
📌3.2:子类构造器
关于构造器,推荐大家先去阅读这篇文章:【Java】一文看懂构造器/构造方法(Cunstructor)。文章很短,方便你更好的理解下面知识。
在之前讲构造器的时候,没有讲到继承。
其实构造器中行首隐含了一条默认语句:super()
: 调用父类构造器,完成对父类属性初始化
public class Dog extends Animal
@Override
public void eat()
System.out.println("啃骨头ing");
public Dog()
//super();
【注意】
- 若父类无参构造器不存在,必须显示调用父类存在的构造器
- super调用父类构造器和this调用本类构造器,有且只能有一条,且必须位于行首(即使用
this()
来调用本类构造器,最终肯定调用到某个构造器中还会调用super()
的,这个不用担心父类没有被初始化)
📌3.3:protected关键字
关于访问修饰符在【Java】一文彻底弄懂访问修饰符(public/protected/默认/private)–建议收藏已经全面介绍过。这里单独谈谈protected
关键字在继承中的作用。
上文已经提到过,子类是无法继承父类被private
修饰的属性,但是我们在封装那一篇文章中讲到,由于种种原因,类中的属性最好为private
。那怎么办呢?
有一个两全其美的办法—protected
关键字:只有子类&同包中其他类可以访问。
📌3.4:final关键字
时刻牢记:我们写的类是现实中的抽象。
真正进入公司,项目中的业务往往比我们举例要复杂很多。意味着,我们写的类会很多,同时类与类之间的关系也会很复杂。
所以,我们并不希望类与类之间继承的层次过深,一般不希望超过三层的继承关系。否则,将对代码进行重构
那如何进行这样的一种限制呢?
可以使用关键字final
对类进行修饰:让类不可被继承(若被继承,将在编译时期报错)
举例:我们平时经常使用的String
类,就是被final修饰的:
C++继承汇总(单继承多继承虚继承菱形继承)
一、C++中的对象模型
1、 概念
语言中直接支持面向对象程序设计的部分;
对于各种支持的底层实现机制。(没看懂……)
2、 类中的成员分类
a) 成员函数
i. static function
ii. non static function
iii. virtual function
b) 数据成员
i. static member data
ii. non static member data
3、 C++对象模型
a) 类对象内存布局中的包括
i. non static member data
ii. vptr(虚函数表指针)
iii. vbptr(虚基类表指针)
b) 不包括
i. static member data(存储在静态存储区)
ii. 成员函数(存储在代码区)
c) virtual table
简称vtbl。存放着指针,这些指针指向该类每一个虚函数。虚表中的函数地址将按声明时的顺序排列。vtbl在类声明后就形成了,vptr是编译器生成的。
d) vptr的位置一般放在一个类对象的最前端。
e) 虚基类表
vbptr指向的表,用于存放虚继承中,虚基类存储相对于虚基类表指针的偏移量。
二、继承类型
1、普通继承(不包含虚函数)
a、单继承
class Base { public: Base (int a = 1):base(a){} void fun0(){cout << base << endl;} int base; }; class Derive:public Base { public: Derive (int a = 2):derive(a){} void fun1(){cout << base1 << endl;} int derive; };
b、多继承
class Base1 { public: Base1 (int a = 2):base1(a){} void fun1(){cout << base1 << endl;} int base1; }; class Base2 { public: Base2 (int a = 3):base2(a){} void fun2(){cout << base2 << endl;} int base2; }; class Derive: public Base1, public Base2 { public: Derive (int value = 4):derive (value){} void fun3(){cout << derive << endl;} int derive; };
c、菱形继承
class Base { public: Base (int a = 1):base(a){} void fun0(){cout << base << endl;} int base; }; class Base1 { public: Base1 (int a = 2):base1(a){} void fun1(){cout << base1 << endl;} int base1; }; class Base2 { public: Base2 (int a = 3):base2(a){} void fun2(){cout << base2 << endl;} int base2; }; class Derive: public Base1, public Base2 { public: Derive (int value = 4):derive (value){} void fun3(){cout << derive << endl;} int derive; };
注:菱形继承存在二义性问题,编译都不通过,只能通过指定特定基类的方式进行访问基类变量。
Derive d;
d.base =3; // 不正确
d.Base1::base = 3; // 正确
2、普通继承(包含虚函数)
a、单继承(包含虚函数)
class Base { public: Base (int a = 1):base(a){} virtual void fun0(){cout << base << endl;} int base; }; class Derive:public Base { public: Derive (int a = 2):derive(a){} virtual void fun0(){}; virtual void fun1(){cout << derive << endl;} int derive; };
注:派生类中新增的虚函数追加到虚函数表后面。
b、多继承(包含虚函数)
class Base1 { public: Base1 (int a = 2):base1(a){} virtual void fun1(){cout << base1 << endl;} int base1; }; class Base2 { public: Base2 (int a = 3):base2(a){} virtual void fun2(){cout << base2 << endl;} int base2; }; class Derive: public Base1, public Base2 { public: Derive (int value = 4):derive (value){} virtual void fun3(){cout << derive << endl;} int derive; };
注:派生类中新增的虚函数,追加到第一个基类的虚函数表的后面。
c、菱形继承(包含虚函数)
class Base { public: Base (int a = 1):base(a){} virtual void fun0(){cout << base << endl;} int base; }; class Base1:public Base { public: Base1 (int a = 2):base1(a){} virtual void fun1(){cout << base1 << endl;} int base1; }; class Base2:public Base { public: Base2 (int a = 3):base2(a){} virtual void fun2(){cout << base2 << endl;} int base2; }; class Derive: public Base1, public Base2 { public: Derive (int value = 4):derive (value){} virtual void fun3(){cout << derive << endl;} int derive; };
注:分析时,由上到下依次分析。存在二义性问题和内存冗余问题。
3、虚继承(不包含虚函数)
新增虚基类指针,指向虚基类表,虚基类表中首项存储虚基类指针的偏移量,接下来依次存储虚基类的偏移量(偏移量是相对于虚基类表指针的存储地址)。
a、单虚继承(不包含虚函数)
class Base { public: Base (int a = 1):base(a){} void fun0(){cout << base << endl;} int base; }; class Base1:virtual public Base { public: Base1 (int a = 2):base1(a){} void fun1(){cout << base1 << endl;} int base1; };
b、多虚继承(不包含虚函数)
class Base1 { public: Base1 (int a = 2):base1(a){} void fun1(){cout << base1 << endl;} int base1; }; class Base2 { public: Base2 (int a = 3):base2(a){} void fun2(){cout << base2 << endl;} int base2; }; class Derive:virtual public Base1, virtual public Base2 { public: Derive (int value = 4):derive (value){} void fun3(){cout << derive << endl;} int derive; };
c、菱形虚继承(不包含虚函数)
第一种形式:
class Base { public: Base (int a = 1):base(a){} void fun0(){cout << base << endl;} int base; }; class Base1:virtual Base { public: Base1 (int a = 2):base1(a){} void fun1(){cout << base1 << endl;} int base1; }; class Base2:virtual Base { public: Base2 (int a = 3):base2(a){} void fun2(){cout << base2 << endl;} int base2; }; class Derive:virtual public Base1, virtual public Base2 { public: Derive (int value = 4):derive (value){} void fun3(){cout << derive << endl;} int derive; };
注:分析派生类的内存分布时,也是由上到下分析。虚继承将基类置于内存末尾,但是置于末尾的顺序也有一定的次序。首先Base先放到末尾,然后Base1放到末尾,最后Base2放到末尾。
第二种形式:
class Base { public: Base (int a = 1):base(a){} void fun0(){cout << base << endl;} int base; }; class Base1:virtual public Base { public: Base1 (int a = 2):base1(a){} void fun1(){cout << base1 << endl;} int base1; }; class Base2:virtual public Base { public: Base2 (int a = 3):base2(a){} void fun2(){cout << base2 << endl;} int base2; }; class Derive: public Base1, public Base2 { public: Derive (int value = 4):derive (value){} void fun3(){cout << derive << endl;} int derive; };
注:分析的原则,从上到下,依次分析。
4、 虚继承(包含虚函数)
a、单虚继承(包含虚函数)
class Base { public: Base (int a = 1):base(a){} virtual void fun0(){cout << base << endl;} int base; }; class Base1:virtual Base { public: Base1 (int a = 2):base1(a){} virtual void fun1(){cout << base1 << endl;} int base1; };
与普通的包含虚函数的单继承相比,派生类拥有自己的虚函数表以及虚函数表指针,而不是与基类共用一个虚函数表。注意虚函数表指针和虚基类表指针的存储顺序。
b、多虚继承(包含虚函数)
class Base1 { public: Base1 (int a = 2):base1(a){} virtual void fun1(){cout << base1 << endl;} int base1; }; class Base2 { public: Base2 (int a = 3):base2(a){} virtual void fun2(){cout << base2 << endl;} int base2; }; class Derive:virtual public Base1, virtual public Base2 { public: Derive (int value = 4):derive (value){} virtual void fun3(){cout << derive << endl;} int derive; };
c、菱形虚继承(包含虚函数)
第一种形式:
class Base { public: Base (int a = 1):base(a){} virtual void fun0(){cout << base << endl;} int base; }; class Base1:virtual public Base { public: Base1 (int a = 2):base1(a){} virtual void fun1(){cout << base1 << endl;} int base1; }; class Base2:virtual public Base { public: Base2 (int a = 3):base2(a){} virtual void fun2(){cout << base2 << endl;} int base2; }; class Derive: virtual public Base1, virtual public Base2 { public: Derive (int value = 4):derive (value){} virtual void fun3(){cout << derive << endl;} int derive; };
第二种形式:
class Base { public: Base (int a = 1):base(a){} virtual void fun0(){cout << base << endl;} int base; }; class Base1:virtual public Base { public: Base1 (int a = 2):base1(a){} virtual void fun1(){cout << base1 << endl;} int base1; }; class Base2:virtual public Base { public: Base2 (int a = 3):base2(a){} virtual void fun2(){cout << base2 << endl;} int base2; }; class Derive: virtual public Base1,virtual public Base2 { public: Derive (int value = 4):derive (value){} virtual void fun3(){cout << derive << endl;} int derive; };
自行脑补C++类对象的内存结构……
注:上述虚函数中,如果派生类重写了基类的虚函数,则对应虚函数表中的虚函数应该修改成重新后的虚函数,即Base::fun()->Derive::fun()。
参考链接:
http://www.cnblogs.com/raichen/p/5744300.html
以上是关于看懂继承|继承入门的主要内容,如果未能解决你的问题,请参考以下文章