有目录哦~写的有点多 面向对象程序设计有 4 个主要特点:抽象、封装、继承和多态性 今天就来说说—继承
has-a, uses-a和 is-a has-a 包含关系,用以描述一个类由多个“部件类”构成。实现 has-a 关系用类成员表 示,即一个类中的数据成员是另一种已经定义的类。 uses-a 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对 象参数传递实现。 is-a 机制称为“继承”。关系具有传递性,不具有对称性。
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类或者子类,产生新类的类称为基类或者父类。继承呈现了面向对象程序设 计的层次结构,体现了由简单到复杂的认知过程。
派生类的声明: class 派生类名:[继承方式] 基类名,[继承方式] 基类名…. { 派生类成员声明; }; 如:
class students:public person//派生类:基类 { //... }继承方式规定了如何访问基类继承的成员。继承方式有public,private, protected。继承方式不影响派生类的访问权限,影响了从基类继承来的成员的访问权限,包括派生类内的访问权限和派生类对象。 附图说明:
此处重点探究基类的保护protected的权限访问变化,因为这个正是因为继承而存在的。 通过上面两幅图可以得出结论: 基类protected成员可以在派生类内访问,而不可以在除基类和派生类的类外使用。
继承可以根据基类的的个数分为单继承、多继承方式。那么这些继承方式的对象内存模型便是重点了,通过对对象模型的探究,我们可以更深入的理解继承的实现。
可以看出:派生类内存分布为,继承于基类的成员在上,自己独有的成员在下。也可以看出,子类继承了父类的所有成员,而不是部分继承。
通过实验可以看出,多继承模型的模型顺序和继承顺序有关系,继承类从右向左以此处于派生类成员之上。
内存模型: 二义性问题: 可以看出,菱形问题引起的问题是子类的子类访问继承来基类的成员,造成二义性问题。当然可以通过附加作用域来指明访问哪一个。这样做的弊端是无法保证同一成员保持一致的内容。 那么如何来解决这个问题呢?????
C++提供了虚拟继承技术来解决这样的二义性和数据冗余的问题 1. 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。 2. 虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体系结构,因为使用虚继承解决数据冗余问题也带来了性能上的损耗
class Father//基类 { public: /*Father() { cout << "father" << endl; }*/ Father(int pub=4, int pro=5, int pri=6) :pub_f(pub), pro_f(pro), pri_f(pri) { cout << "this is son class! father()" << endl; } ~Father() { cout << "this is son class! ~father()" << endl; } void show_fnum() { cout << "pub_f=" << pub_f << endl; cout << "pro_f=" << pro_f << endl; cout << "pri_f=" << pri_f << endl; } void who() { cout << "father" << endl; } void setnum(int a, int b, int c) { pub_f = a; pro_f = b; pri_f = c; } int pub_f; protected: int pro_f; private: int pri_f; }; ////////////////////////////////////////public class Son1 :virtual public Father //virtual 关键字!!!! { public: //Son1()//缺省 //{ // cout << "son1" << endl; //} Son1(int pubs=7, int pros=8, int pris=9)//参数列表 : pub_s(pubs) , pro_s(pros) , pri_s(pris) { cout << "this is son class! son()" << endl; } ~Son1() { cout << "this is son class! ~son()" << endl; } void show_snum() { cout << "pub_s=" << pub_s << endl; cout << "pro_s=" << pro_s << endl; cout << "pri_s=" << pri_s << endl; cout << "pub_f=" << pub_f << endl; cout << "pro_f=" << pro_f << endl; //cout << "pri_f=" << pri_f << endl;//基类(父类)的私有成员不可以被子类(继承类)访问 } void who() { cout << "son1" << endl; } int pub_s; protected: int pro_s; private: int pri_s; }; class Son5 :virtual public Fathervirtual 关键字!!!! { public: Son5(int a=10) :son_5(a) {} private: int son_5; }; class Grandson :public Son1, public Son5 { public: Grandson() { } private: int grandason; };添加一个virtual关键字 在继承方式前面就解决问题了?什么原呢???????? 来看看内存! 神奇的事情发生了,在成员前面多了一个像地址一样的东西?这是什么呢?对,正是因为这个地址的存在才解决了二义性问题。 这个地址指向一个偏移量的表,在图中已经说明。每个表记录了1,相对自己的类的偏移量,2,相对基类的偏移量。通过这个偏移量可以精确地查找到基类的成员。那么这样就解决了这个二义性问题。图中已经详细标明。好好看哦~ 再来看看反汇编怎么实现访问基类的成员的: 正如推测那样,通过偏移量精确查找到基类的成员喽。
对以上内容做几点补充哈: 一。。继承体系中的作用域 1. 在继承体系中基类和派生类是两个不同作用域。 2. 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以 使用 基类::基类成员 访问)–隐藏 –重定义 3. 注意在实际中在继承体系里面最好不要定义同名的成员。 二。。继承与转换–赋值兼容规则–public继承 1. 子类对象可以赋值给父类对象(切割/切片) 2. 父类对象不能赋值给子类对象 3. 父类的指针/引用可以指向子类对象 4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成) 三。。友元与继承 友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员 四。。继承与静态成员 基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。 OK!欢迎指点一二。