继承(上)
继承是面向对象编程中一个非常重要的特性,它也是另一个重要特性——多态的基础。
4.1继承概念的引入
现实生活中的事物都归属于一定的类别。比如,狮子是一种( IS_A)动物。为了在计
算机中摸拟这种关系,面向对象的语言引入了继承(inherit)的特性。
如图 所示,用类 Animal代表动物,用类 Lion代表狮子,用一个空心的三角箭头表
示继承关系。
构成继承关系的两个类中,Animal称为父类(parent class)或基类(base class),Lion
称为子类(child class)。
提示:
在一些书中,将父类称为超类(super class)。
“继承”关系有时又称为“派生”关系,“B 继承自 A”,可以说为“B 派生自 A”,或
反过来说,“A 派生出 B”。
父类与子类之间拥有以下两个基本特性:
- 是一种(IS-A)关系:子类是父类的一种特例。
- 扩充(Extends)关系:子类拥有父类所没有的功能。
以下 C#代码实现了狮子类与动物类之间的继承关系:
可以看到,C#中用一个冒号间隔开父类和子类。
4.2类成员的访问权限
面向对象编程的一大特点就是可以控制类成员的可访问性。当前主流的面向对象语言都
拥有以下三种基本的可访问性(表 2):
表 2类成员的访问权限
(1)public和 private
public和 private主要用于定义单个类的成员存取权限。
请看以下示例代码:
public class A
{
public int publicI;
private int privateI;
protected int protectedI;
}
当外界创建一个 A的对象后,只能访问 A的公有实例字段 publicI:
A a = new A();
a.publicI = 100; //OK!
类 A的私有实例字段 privateI只能被自身的实例方法所使用:
public class A
{
public int publicI;
private int privateI;
protected int protectedI;
private void f()
{
privateI = 100; //OK!
}
}
上述代码中,类 A的私有方法 f()访问了私有字段 privateI。注意,只要是类 A直接定义
的实例方法,不管它是公有还是私有的,都可以访问类自身的私有实例字段。
(2)protected
在形成继承关系的两个类之间,可以定义一种扩充权限——protected。
当一个类成员被定义为 protected之后,所有外界类都不可以访问它,但其子类可以访
问。
以下代码详细说明了子类可以访问父类的哪些部分(示例项目 Inherits):
class Parent
{
public int publicField=0;
private int privateFiled=0;
protected int protectedField=0;
protected void protectedFunc()
{ }
}
class Son:Parent
{
public void ChildFunc()
{
publicField = 100;//正确!子类能访问父类的公有字段
privateFiled = 200;//错误!子类不能访问父类的私有字段
protectedField = 300; //正确!子类能访问父类的保护字段
protectedFunc(); //正确!子类能访问父类的保护方法
}
}
当创建子类对象后,外界可以访问子类的公有成员和父类公有成员,如下所示:
由此可见,可以通过子类对象访问其父类的所有公有成员,事实上,外界根本分不清
楚对象的哪些公有成员来自父类,哪些公有成员来自子类自身。
小结一下继承条件下的类成员访问权限:
- 所有不必让外人知道的东西都是私有的。
- 所有需要向外提供的服务都是公有的。
- 所有的“祖传绝招”,“秘不外传”的都是保护的。
(3)internal
C#中还有一种可访问性,就是由关键字 internal所确定的“内部”访问性。
internal有点像 public,外界类也可以直接访问声明为 internal的类或类的成员,但这只
局限于同一个程序集内部。
读者可以简单地将程序集理解为一个独立的 DLL或 EXE文件。一个 DLL或 EXE文件
中可以有多个类,如果某个类可被同一程序集中的类访问,但其他程序集中的类不能访问它,
则称此类具有 internal访问性。
例如类库项目 ClassLibrary1可以生成一个独立的程序集(假定项目编译后生成
ClassLibrary1.DLL),其中定义了两个类 A和 B:
namespace ClassLibrary1
{
internal class A
{
internal int InternalI=0;
}
public class B
{
public void f()
{
A a = new A(); //OK!
a. InternalI= 100; //OK!
}
}
}
由于类 B与类 A属于同一个程序集,所以,B中的代码可以创建 A的对象,并访问 A
的声明为 internal的成员 InternalI。
在程序集 ClassLibrary1.DLL之外,外界只能创建声明为 public的类 B的对象,不能创
建声明为 internal的类 A的对象。
internal是 C#的默认可访问性,这就是说,如果某个类没有任何可访问性关键字在它前
面,则它就是internal的。
比如上面的类 A也可以写成:
它完全等同于
但要注意,在类中,如果省略成员的可访问性关键字,则默认为 private的。
例如:
相当于
为便于读者查阅,将 C# 2.0和 Visual Basic 2005的存取权限总结如表 3所示。
表 3类型存取权限一览表
4.3子类父类变量的相互赋值
构成继承关系的父类和子类对象之间有一个重要的特性:
子类对象可以被当成基类对象使用。
这是因为子类对象本就是一种(IS_A)父类对象,因此,以下代码是合法的:
Parent p;
Son c = new Son ();
p = c; //正确,子类对象可以传给父类变量
上述代码中 Parent是 Son类的父类。
然而,反过来就不可以,以下代码是错误的:
c = p; //错误,父类对象变量不可以直接赋值给子类变量
如果确信父类变量中所引用的对象的确是子类类型,则可以通过类型强制转换进行赋
值,其语法格式为:
子类对象变量=(子类名称)基类对象变量;
或使用 as运算符
子类对象变量=基类对象变量 as 子类名称;
示例代码如下:
c = (Child)p; //正确,父类对象变量引用的就是子类对象
或
c = p as Child ;