6.面向对象三大特性---继承
一.概述
1.使用继承的目的
·继承就是为了实现软件工程的目的:高类聚,低耦合
·高类聚:任何一个类,让它的成员尽量多,即把内容关联度高的放在一起的放在一起
·低耦合:不同的类之间重合少,即降低每个功能模块之间的关联度,互相依赖少
2.用两种方式看待类之间的关系
以马来举例。
2-1 集合思想:白马、黄马都是马。马包含白马、黄马

2-2 数据思想:不管是白马还是黄马,除了是马以外,还有自己的特性(比如颜色)。白马包含马,黄马也包含马,相当于数学中的交集

3.基类与子类
继承就是使用数据思想去看待基类(马)与子类(白马、黄马)
·基类:大的分类,少的共同特性
·子类(派生类):小的分类,多的特性
二.继承于派生
1.派生类继承基类的方式
·class 派生类名:继承方式 基类类名 { };
·举例:class B : public A { };
2.派生类是怎么去继承基类的成员变量与成员函数的
·成员变量
(1) 派生类对象中包含基类对象
·即在构造派生类对象时,会先构造出一个基类对象。派生类大小 = 基类所有成员变量大小 + 派生类所有成员变量大小。
(2) 成员变量由偏移量决定
·偏移量,指的是一个地址段到另一个地址段之间的距离
·构造出的对象,其实就是一个内存段,对象就相当于是保存了一个内存段首地址。
·成员变量在编译器存储的方式是,每个成员变量相对于对象首地址的偏移量,即成员变量在类中的位置是,对象首地址+偏移量。
·成员函数
(1) 拷贝偏移量:同成员变量
(2) 类型决定代码段中访问那个函数
class A { int a; public: A() { a = 1; } void show() { printf("A:%d\n",a); } }; class B { int b; public: B() { b = 2; } void show() { printf("B:%d\n",b); } }; int main() { A a; B* b = (B*)&a; b->show();
cout << "对象a的首地址" << &(a) << endl;
cout << "对象b的首地址" << b << endl;
cout << "" while(1); return 0; } //调用的是B类的show,显示的是A类变量a的值 //B::1
//1.函数调用:对象b的类型是B类,所以调用的B类的show()。可以理解为函数是与变量类型绑定的
//2.变量调用:对象b保存的首地址是对象a的,由于变量int b;在B类中的偏移量是0,而变量int a;在A类中的偏移量也是0,
在类中访问成员变量的方式为首地址+偏移量,故最终得到的是变量int a;的值
3.继承中的封装
·封装的三种方式
private:只有本类对象的成员函数可以访问类中用private修饰的函数与变量
protected:派生类对象的成员函数可以访问基类中用protected修饰的函数和变量
public:类中,类外都能访问用public修饰的函数和变量
·对象中的封装
在类中的封装,限制成员变量与函数,在本类外的访问权限
·继承过程中的封装
稳妥为主,限制基类成员变量与函数,在基类之外的访问权限
举例:基类中使用的private,派生类继承的方式是public,那么结果为private
基类中使用的public,派生类继承的方式是private,那么结果同样为private
4.继承中的构造与析构
·构造
派生类对象构造过程:先调用基类构造器,再调用派生类构造器
如果希望构造派生类对象中继承自基类对象的成员变量,必须调用基类构造器
class A { int a; public: A(int x):a(x){}; } class B { int b; public: //A(x),就是调用A类的构造函数 B(int x,int y):A(x),b(y){}; }
·析构
派生类对象析构过程:与构造相反,先调用派生类析构器,再调用基类析构器
5.多重复杂继承
·继承的三大原则
一个类可以被多个类继承,一个类也可以继承自多个类,自己不能继承自己
·多重复杂继承的经典例子:菱形继承
图解:

示例:
//基类A class A { int a; public: A(int x):a(x){}; }; //派生类B class B :public A { int b; public: B(int x,int y):A(x),b(y){}; }; //派生类C class C :public A { int c; public: C(int x,int y):A(x),c(y){}; }; //派生类D class D :public A { int d; public: D(int x,int y):A(x),d(y){}; }; //派生类E class E :public B, public C, public D { int e; public: E(int a,int b,int c,int d,int f):B(a,d),C(b,d),D(c,d),e(f){}; };
int main()
{
E e;
/*
此时对象e中有以下这些成员变量
e
B::A::a
B::b
C::A::a
C::c
D::A::a
D::d
*/
return 0;
}
6.虚继承
·目的:只是为了节约一个对象
举例:有一个A类,B类继承A类,C类也继承A类,D再继承B类和C类。
类D继承自类B和类C,而B类和C类都继承自类A,类D中会两次继承A,为了节省空间,可以将B、C对A的继承定义为虚拟继承,而A就成了虚拟基类,B、C就都使用这个虚拟基类,不用再去构造A类了。
·virtual 关键字修饰继承过程
class A { int a; public: A(int x):a(x){}; }; class B :virtual public A { int b; public: B(int x,int y):A(x),b(y){}; };
·虚继承的继承过程提前:先构造出虚继承的类,为了让其他类在继承的时候直接使用
class A { public: A() { cout << "A类构造" <<endl; } }; class B { public: B() { cout << "B类构造" <<endl; } }; class C:public A,virtual public B { public: C() { cout << "C类构造" <<endl; } };
//正常流程: A类构造->B类构造->C类构造
//virtual修饰后流程:B类构造->A类构造->C类构造
·继承同一个类的时候都是虚继承,则共用同一个基类构造器
通过Virtual Studio编译器的监视,可以发现,在delete d;之后,A类对象是无法访问内存的,间接说明了虚拟出来的类,只是临时存在的。
//基类A
class A { int a; public: A() { a = 1; cout << "A类构造" <<endl; } };
//派生类B,虚继承 class B:virtual public A { int b; public: B() { b = 2; cout << "B类构造" <<endl; } }; //派生类c,虚继承 class C:virtual public A { int c; public: C() { cout << "C类构造" <<endl; } };
//派生类D class D:public B,public C { int d; public: D() { cout << "D类构造" <<endl; } };
int main()
{
D* d = new D();
delete d;
return 0;
}
//正常流程: A类构造->B类构造->A类构造->C类构造->D类构造
//virtual修饰后流程:A类构造->B类构造->C类构造->D类构造

浙公网安备 33010602011771号