前置知识:public成员:类域内外均可随意访问 private与protected成员:类域内可随意访问,类域外不准访问,但是在继承状态中,子类类域中,对于父类的private成员不可访问,但对于protected成员可以访问
一.继承
1.继承方式:
基类(父类)->继承->子类(衍生类)
2.继承权限
总结:基类的私有成员在子类都是不可见;基类的其他成员在子类的访问方式就是访问限定符和继承方式中权限更小的那个(权限排序:public>protected>private)。使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但最好显式地写出继承方式。在实际使用时一般都是public继承,几乎很少使用protetced/private继承,也不提倡使protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,扩展和维护性不强。
3.最终类(不可被继承的类)
在类后面加 final 表明此类为最终类 不可被继承
4.子类中的父类
继承不是拷贝,子类继承父类并不是将父类的内容拷贝一份给子类。但我们在问题分析中可以当作子类中有一份父类,这样便于分析问题。
class A
{
public:
virtual void test()
{
cout << "a";
}
};
class B:public A
{
public:
virtual void test()
{
cout << "b";
}
private:
int b;
};
void main()
{
A* a = new A;
B* b = new B;
a = b;
a->test();
b->test();
}
main函数中a、b指针变化分析如下:
因此输出结果应该为a,b
进阶一下:
class A{//...}
class B:public A{//...}
void main()
{
A aa;
B bb;
A* ptr = &bb;
A& ref = bb;
A tmp = bb;
}
5.继承与组合(is a与has a)
继承是一种is a的关系,即子类是父类的一个延申
组合是一种has a的关系,为了对应 此处亦称为子类与父类,子类之中有父类
继承与组合如何区分应用呢?
举个例子:宝马汽车(子类)上有轮胎(父类),宝马与轮胎是组合关系
宝马汽车(子类)是一种汽车(父类),宝马与汽车是继承关系
那么什么是组合呢?
从程序上来说,组合的本质就是在子类中定义一个A类对象,通过这个对象来访问A类中的成员。
组合与继承区别:继承没有声明具体的父类对象,对于父类中protected对象,由于子类继承了父类,可认为父类与子类在同一个作用域,因此子类可以使用父类protected对象。但是组合情况下,本质是创建了一个父类对象,此时已经处于父类类域之外,因此无法访问父类protected对象。
6.继承类中默认成员函数
构造函数:先构造基类再构造衍生类
析构函数:先析构衍生类再析构基类,为防止人为书写时写反,因此基类析构函数自动调用,可不必自行书写
7.菱形继承与虚继承
事先说明:菱形继承能不写就不写,能不碰就不碰,大坑!!!!!!!
此时D类中就会含有B类中的A与C类中的A,这不但会造成存储浪费还会造成二义性。举个例子,假如A类是 个人信息类 B类是教师 C类是 博士后。D类是师资博后,那么D类的对象中B与C中A都应该是这个师资博后的个人信息,但因为B中的A与C中的A可以不一样,因此我们就有可能看到B类中师资博后是35岁 而C中的A师资博后是30岁,这显然是不合理的,当然也有人说我们输入信息时保证B、C中的A信息一样就可以了,那这种人为操作必然带来失误,到时候就不好确定真实信息了,再且这也造成了多存了一份信息,浪费空间
那么菱形继承如何解决呢?虚继承
class A{//...}
class B:virtual public A{//...}
class C:virtual public A{//...}
class D:public B,public C{//...}
虚继承以后B、C共用同一份A
二.多态(后续补充)
前言:多态建立在继承的基础上,没有继承,无从谈起多态 多态与virtual函数紧密相关
此处虚函数与虚继承仅关键字virtual相同,其余再无任何关系
1.虚表
2.重载 重写(覆盖) 重定义(隐藏)
重载发生于同一个作用域之下,函数名相同,参数位置、数量、类型不同可构成函数重载;
重写发生于多态子类虚函数覆盖父类虚函数的过程,重写需要父子函数 三同(函数名、参数、返回类型)
重定义 继承范围内,不是重写就是隐藏
3.多态下的析构函数
父类析构函数要加virtual 让子类进行重现不然下面这个情景会坑:
class A{//...}
class B:public A{//...}
int main()
{
A* ptr= new B;
delete ptr;
return 0;
}
正是因为子类重写了析构函数,在ptr调用析构函数时才能先调用衍生类析构再调用父类析构
原因分析如下:
4.纯虚函数(抽象类)