一、继承的思想
继承关系
继承就是不可修改原有的类,直接利用原来的类的属性和方法并进行扩展。原来的类称为基类,继承的类称为派生类,他们的关系就像父子一样,所以又叫父类和子类。通俗的讲,继承是一种从一般到特殊的关系,是一种“is a”的关系,即子类是父类的拓展,是一种特殊的父类。
继承可以解决类与类之间代码重复的问题,同时也体现了一个体系。
当父类成员用public修饰、父类成员用protected修饰、在同一个包中的父亲中的缺省成员是可以继承的;
但当父类成员用private修饰时,可以被继承但子类无法直接访问,可以通过父类的非private方法访问;
最后,父类的构造器,不可继承。
方法覆盖
覆盖:
当父类的某一个行为不符合子类具体的特征的时候,此时子类需要重新定义父类的方法并重写方法体。只有方法存在覆盖的概念,字段没有。
一同:
实例方法签名必须相同(方法签名=方法名+方法的参数列表)
两小:
①子类方法的返回值类型和父类方法或者是其子类方法返回值相同。子类可以返回一个更加具体的类型。
②子类方法声明抛出的异常类型和父类方法声明抛出异常类型相同或者是其子类子类方法中声明抛出的异常小于或等于父类方法声明抛出异常类型;子类方法可以同时声明抛出多个属于父类方法声明抛出异常类的子类。
一大:
子类方法的访问权限比父类方法访问权限更大或者相等。而private修饰的方法不能被覆盖,所以不存在覆盖的概念。
@Override标签:若方法是复写方法,在方法前或方法上贴上该标签,编译通过,否则编译报错。
方法重载和方法覆盖的区别:
方法重载:
作用:解决了同一各类中,相同功能的方法名的不同的问题。既然相同的功能,那么名字就应该相同。
规则:方法同名,参数列表必须不一致,可用于一个类中的所有方法且可被重载多次。
方法覆盖:
作用:解决了子类继承父类之后,可能父类的某一个方法不满足子类的具体特征,此时需要重新在子类中定义该方法,并重新写方法体。
规则:子类和父类的方法名称,参数列表,返回类型必须完全相同,而且子类方法的访问修饰符的权限不能比父类低。
隐藏
①在满足继承的的访问权限下,隐藏父类静态方法;若子类定义的静态方法的签名和超类中的静态方法签名相同,那么此时就是隐藏父类方法。注意:仅仅是静态方法,子类存在和父类一模一样的静态方法
②满足继承的访问权限下,隐藏父类字段:若子类中定义的字段和超类中的字段名相同(不管类型),此时就是隐藏父类字段,此时只能通过super访问被隐藏的字段
③隐藏本类字段:若本类中某局部变量名和字段名相同,此时就是隐藏本类字段,此时只能通过this访问被隐藏的字段
ps:static不能与supper/this共存
super关键字
this:表当前对象
super:表当前对象的父类对象
注意事项上文已提及
子类初始化的过程
先进入子类构造器,然后在构造器中会先调用父类构造器,再执行子类构造器代码。
如果弗雷不存在可以被子类访问的构造器,则不能存在子类;如果父类没有提供无参构造器,此时子类必须显示通过super语句去调用父类带参的构造器。
Object类
object类是所有类的父类或间接父类
所有对象都实现这个类的方法
Object类的方法有:
1.Object()
Object类的构造方法
2.registerNatives()
为了使JVM发现本机功能,他们被一定的方式命名。例如,对于java.lang.Object.registerNatives,对应的C函数命名为Java_java_lang_Object_registerNatives。
3.clone()
clone()函数的用途是用来另存一个当前存在的对象。只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
(注意:回答这里时可能会引出设计模式的提问)
4.getClass()
final方法,用于获得运行时的类型。该方法返回的是此Object对象的类对象/运行时类对象Class。效果与Object.class相同。
5.equals()
equals用来比较两个对象的内容是否相等。默认情况下(继承自Object类),equals和==是一样的,除非被覆写(override)了。
6.hashCode()
该方法用来返回其所在对象的物理地址,常会和equals方法同时重写,确保相等的两个对象拥有相等的hashCode。
7.toString()
toString()方法返回该对象的字符串表示每个类都应该覆盖toString()方法,返回想要的数据
8.wait()
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
9.wait(long timeout)
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
10.wait(long timeout, int nanos)
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
11.notify()
唤醒在此对象监视器上等待的单个线程。
- notifyAll()
唤醒在此对象监视器上等待的所有线程。
13.finalize()
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
二、虚函数,多态的思想
虚函数:被virtual关键字修饰的成员函数
在有虚数覆盖的虚函数表中:
覆盖的函数被放到了虚表中原来父类虚函数的位置。
没有被覆盖的函数依旧保持原样
内存中的虚函数表的父类函数的位置已经被子类函数地址所取代,于是在实际调用发生时,是调用子类函数,从而实现多态。
多态是用父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。
通俗的讲就是“一个接口,多种表现/实现”,多态性是提供一种技术,这种技术允许将父类指针或引用赋值为子类的指针或引用,进行这种赋值之后,在运行时,可根据当前赋给它的的子对象的特性以不同方式运行。
多态可以让父类的指针具有多种形态,即不需要改动很多代码就可以让父类这一种指针做多个子类指针的事情。而虚函数表也正是为了实现多态的机制而产生的。
而多态的主要作用是实现接口复用,把不同的子类对象都当做父类来用,屏蔽不同子类对象之间的差异,编写通用的代码,实现通用的编程,以应对需求的变化。
三、纯虚函数和抽象类
纯虚函数是一个在基类中只有声明的虚函数,在基类中无定义。要求在任何派生类中都定义自己的版本;纯虚函数为各派生类提供一个公共界面(接口的封装和设计,软件的模块功能划分)
通俗的讲,纯虚函数是没有函数体,同时在定义的时候,其函数名后面要加上“= 0”。纯虚函数也一定是某个类的成员函数。
在虚函数表当中,如果是纯虚函数,那么就实实在在的写上0,如果是普通的虚函数,那就肯定是一个有意义的值。
把包含纯虚函数的类称之为抽象类。
对于抽象类来说,C++是不允许它去实例化对象的。也就是说,抽象类无法实例化对象。同时对于抽象类的子类也可以是抽象类。而且对于抽象类的子类来说,只有把抽象类中的纯虚函数全部实现之后,那么这个子类才可以实例化对象。
四、虚继承
为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。
在继承方式前面加上 virtual 关键字就是虚继承
虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class)
在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
但是,必须在虚派生的真实需求出现前就已经完成虚派生的操作。换言之,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。
同时,因为在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。
ps:不提倡在程序中使用多继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多继承,能用单一继承解决的问题就不要使用多继承。