1. 继承miscs
- 被声明为local的数据成员或方法只能对自身可见,而对于外部和子类不可见;
- 被声明为protecte的数据成员或方法,对外部不可见,对于自身和子类可见。
- 我们知道一个类的对象被定义的时候是一个空的句柄, 当其构造函数被调用的时候才分配空间, 其句柄也指向该片空间的入口地址。 同样, 当一个派生类的对象被定义的时候, 它也是一个空句柄, 当其构造函数被调用的时候, 分配的空间不仅有来自父类的继承而来的数据和方法, 还有自己添加的部分; 其中新添加的数据成员和方法存储在独立的空间中, 保持与继承成员和方法的独立性。

1.1 子类对象与父类对象的赋值
子类对象也是它们的父类对象的有效表示。 例如, 每一个 DerivedPacket对象都是一个完全合法的 Packet对象。 一个 DerivedPacket 对象可以赋值给一个 Packet的对象变量:
DerivedPacket lp = new;
Packet p = lp;
为此, 子类对象可以赋值给父类对象。 在这个过程中有两个需要注意的问题。
1 ) 如何处理一个子类对父类的赋值过程?
2) 如何访问和引用重写和新增的数据成员和方法?
其中, 赋值过程中遵循以下规则。
- 子类对象是父类对象的有效表示, 可以赋值给父类对象。
- 父类对象可以通过$cast 的方式尝试给子类对象赋值, 并判定是否赋值合法。
在访问对象的过程中遵循一下规则: 通过父类的对象去引用在子类中重写的属性或方法, 结果只会调用父类的属性或方法; 通过子类对象可以直接访问重写的属性或方法; 在子类扩展过程中新增的属性和方法对于父类对象不可见; 子类可以通过super操作符访问父类中的属性和方法, 以区分于本身重写的属性和方法。
一个子类的对象赋值给一个父类的对象是合法的; 而若一个父类对象直接赋值给子类对象就是不合法的, 但是假如一个父类对象指向的是一个子类对象,那么把该父类对象赋值给另外一个子类对象, 则是合法的。 为了检查一个父类对象对一个子类对象赋值是否合法, 我们可以使用动态的类型转换$cast。
1.2 构造函数调用
一个子类在实例化的时候会调用其构造函数 new()为其分配空间。 在该函数中定义的任何代码执行之前,new()执行的第一个默认动作是调用其父类的构造函数 new() ,并且会沿着继承关系按这种方式一直向上回溯调用。 因此, 所有的构造函数会按正确的顺序调用执行, 它们都是起始于根基类并结束于当前的类。

2. 虚方法和多态
类中的方法在定义的时候通过添加virtual关键字来声明一个虚方法,虚方法是一个基本的多态性结构。虚方法可以重新其所有基类中的方法,然而普通的方法被重写后只能在本身及其派生类中有效。从另一个角度解释,每个类的继承关系只有一个虚方法的实现,而且是在最后一个派生类中。
一个方法在被重写的时候可以声明为虚方法; 一旦方法被声明为虚方法, 它在后续的继承过程中就永远是一个虚方法, 不管重写的时候是否使用virtual。 也就是说, 虚方法在其子类扩展、 重写的时候virtual是可选的而不是强制的。
封装可以隐藏实现细节, 使得代码模块化, 继承可以扩展已存在的代码模块, 它们的目的都是为了代码重用, 而多态则是为了实现另外一个目的: 接口重用。
实现了多态需要以下三个步骤:
1 在父类中定义虚方法。 如 BasePacket 类中的虚方法printB。
2 在子类中重写父类中的虚方法。 如My_Packet 中对printB的重写。
3 申明父类对象的变量 ( 如 P1) , 该变量既可以指向父类对象 ( 如P1自身) , 也可以指向子类对象 ( 如 P2) 。 当变量指向父类对象时, 调用的是父类方法; 当变量指向子类对象时, 调用的是子类同名方法。 为此, 当父类的对象指向不同的子类对象的时候, 虚方法( 如 printB) 可以表现出不同的实现方法, 而它们都可以通过统一的父类对象实现访问。