继承是VB6.0编程人员盼望已久的面向对象编程特性。在这套书的VB篇中,笔者不得不为每种图元都定义了颜色、线宽和线型等属性。有了继承以后,只需要在基类中定义它们,并在派生类中进行继承就行了。
对继承关系的描述,最常用的说法是,是\"is-a\"的关系,即\"是一种\"的关系。比如老虎是一种动物,香蕉是一种水果等。继承是一种很有用的面向对象特性,与它有关的概念包括基类、派生类、抽象基类、重写、遮蔽等。
基类
继承是一个体系,是多个对象之间的一种关系。在这个体系里面,相邻的两个继承层次中处于上面一个层 的那个类可那些类称为基类,又称为父类,她于下面一个层次的那个人或那些类称为派生类或子类。基类在它所属的概念层次上描述对象的特征和行为。④下面结合一个例子来说明继承关系。点、直线段、圆和圆弧等都是图元的一种,它们都有编号、颜色、线宽和线型等属性,都有绘图等方法。可以考虑建立一个图元基类CGElement,然后在它的基础上派生其他图元类。作为演示,下面建当一个简单的CGElement类。
【VB.NET】
Public Class CGElement
Private m_ ID As Integer
Public Property ID() As Integer
Get
Return m_ID
End Get
Set(ByVal Value As Integer)
m ID=Value
End Set
End Property
Public Sub New()
End Sub
Public Sub Draw()
text-blue-400">Console.WriteLine"绘图元。")
End Sub
End Class
【VC#.NET】
public class CGElement
{
private int m_ID;
public int ID
{
get return m_ID;}
set(m_ID=value;}
}
public CGElement()
{
}
public void Draw()
{
Console.WriteLine("绘图元。");
}
派生类
古时候有一则侧笑话是这么说的:一个人照着别人描述的样子去找千里马,他老想着这千里马眼要突、蹄要怎么的,结果找回来一只大青蛙。对了,是按图索骥。笑话的本意是要我们做事不要生搬硬套,否则要闹笑话。那么这人的问题出在哪里呢?他显然忽略了他要找的首先是马,然后才是千里马这个问题。
切换到语言环境,这个问题就是基类与派生类的关系问题。显然,这里马是基类,千里马是派生类。派生类首先从基类那里继承成员,使自己具有基类的基因,然后才有自己的区别于其他派生类的特征。关于马和千里马,大致还有以下这样一些排列组合。
(1)千里马也是马。
(2)先有马,后有千里马。
(3)想做千里马,先做马。
后面要讲到的重写和遮蔽等都在派生类中实现。在VB.NET和VC#.NET中分别用Inherits关键字和冒号(:)来表示继承关系,如:
【VB.NET】
Public Class Cline
Inherits CGElement
【VC#.NET】
public class CLine:CGElement
派生类中可以继承基类的公共成员和受保护成员。如最常用的Fom1类就继承了System.Windows.Forms名字空间中的Form类。当我们在Forml内部使用Me这个关键字来调用自身的成员时,会显示一个成员列表,如图2-1所示。在没有新建成员的情况下,该成员列表中的成员都是从基类中继承来的。
图2-1 Forml类的继承成员
下面创建一个CLine类,它描述直线段的特征和行为。直线段是一种图元,所以直线段类CLine与图元类CGElement之间存在继承关系。定义这种继承关系以后,CLine类就能继承CGElement类的属性和方法。下面从CGElement类派生CLine类,它继承基类的ID属性和Draw方法。
【VB.NET】
Public Class CLine
Inherits CGElement
Private m_Begin,m_End As PointF
'定义直线段的起点和终点属性
‘…
'构造函数
Public Sub NewO
End Sub
End Class
【VC#.NET】
public class CLine:CGElement
{
private PointF m_Begin,m_End;
//定义直线段的起点和终点属性
//…
//构造函数
public Cline{
{
}
}
在Form1类中输入下面的代码,对CGElement类和CLine类进行测试。代码中首先分别创建一个CGElement类实例ge和一个CLine类实例line;然后将line的继承成员ID属性设置为10,并在输出窗口中输出它:最后绘制直线段line。
【VB.NET】
Private Sub Form1_Load(ByVal sender As System.Object,
ByVal text-blue-400">e As System.EventArg) Handles MyBase.Load
Dim line As New Cline()
line.ID =10
text-blue-400">Console.text-blue-400">WriteLine("直线段的ID号为:{0}", line.ID)
line.Draw()
End Sub
【VC#.NET】
private void Form1_Load(object sender, System.EventArgs e)
{
CLine line=new Cline();
line.ID=10;
Console.WriteLine("直线段的ID号为:{0}",line.ID);
line.Draw();
}
运行程序,输出窗口中的结果如图2-2所示。
图2-2输出窗口中的结果
抽象基类
前面创建的CGElement类作为CLine类的基类是可以实例化的,可以用New关键字或new关键字创建它的实例。但如果现在只想把CGElement类作为一个基类使用,因为作为\"图元\"来讲,它只是一个抽象的概念,并没有可见的形体,所以创建一个它的实例并没有多大的实际意义。
将基类创建为抽象基类,使用VB.NET时需要用到Mustinherit 关键字,使用VC#.NET时需要用到abstract关键字。必须重写的抽象方法需要用MustOverride 关键字或 abstract关键字进行标示。
下面将前面的CGElement类改成抽象基类。
【VB.NET】
Public MustInherit Class CGElement
'此处代码与前面的相同
Public MustOverride Sub Draw()
End Class
【VC#.NET】
public abstract class CGElement
{
//此处代码与前面的相同"
public abstract void Draw();
}
将CGElement类改成抽象基类以后,就不能用New关键字或new关键字进行实例化了。在派生类中必须重写抽象方法Draw。下面介绍重写。
重写
重写,或者说覆盖,指的是在派生类中改写从基类中继承来的方法,用另外的方式来实现它,以实现个性。
如果基类的方法为抽象方法,或者是用Overridable(VB.NET中)或virtual(VC#.NET)关键字声明了的可以重写的方法,则在派生类中可以重写该方法。比如在基类CGElement类中有一个可以重写的方法Draw,则派生类CLine类和CRectangle类等中可以重写该方法,分别实现直线段和矩形的绘制。在派生类中重写方法需要使用Overrides关键字或override关键字。
下面接着2.2.3节的例子,在派生类CLine类中重写Draw方法。
【VB.NET】
Public Overrides Sub Draw()
text-blue-400">Console..text-blue-400">WriteLine("绘直线段。")
End Sub
【VC#.NET】
public override void Draw()
{
Console.WriteLine("绘制直线段。");
}
遮蔽
使用遮蔽也可以重写基类的方法,但遮蔽不仅仅是重写基类的方法,它还可以替代基类的方法。例如,在VB.ET中,如果基类的方法有两个参数,使用遮蔽,可以在派生类中用带有3个参数的同名方法替代它。另外,使用遮蔽方法重写基类的方法时,不需要在基类中将方法声明为Overridable或virtual。
下面的代码首先创建一个基类CBase,它有一个Add方法,返回两个参数的和:然后创建一个派生类CDerive,.在其中遮蔽基类的Add方法,在两个参数的和的基础上再加上l00以后返回;最后在Forml类中调用CDerive类实例的Add方法。
创建项目以后,首先创建一个CBase类。
【VB.NET】
Public Class CBase
Public Function Add(ByVal a As Single,ByVal b As Single)As Single
Return a+b
End Function
End Class
【VC#.NET】
public class CBase
{
public CBase(
{
}
public float Add(float a,float b)
{
return a + b;
}
}
然后创建CDerive类,代码如下:
【VB.NET】
Public Class CDerive
Inherits CBase
Public Shaows Function Add(ByVal a As Single, ByVal b As Single) As Single
Return a+b+100
End Function
End Class
【VC#.NET】
public class CDerive:Cbase
{
public Cderive()
{
}
public new float Add(float a, float b)
{
retum a+b+100;
}
}
在Form1类中添加下面的代码,创建一个CDerive类实例sum,然后调用它的Add方法,根拥给定的参数10和20求和。此时基人返回的值将是130,而不是30。
【VB.NET】
Private Sub Forml_Load(ByVal sender As System.Object,_
ByVal text-blue-400">e As System.EventArgs) Handles MyBase.Load
Dim sum As New Cderive()
sum.Add(10, 20)
End Sub
【VC#.NET】
private void Form1_Load(object sender,System.EventArgs e)
{
CDerive sum=new CDerive();
sum.Add(10,20);
}
重载
前面在介绍类的时候曾经讲解了重载技术。这里主要介绍在有继承的情况下如何使用重载。如果要重载从基类中继承来的方法,在VB.NET中,必须使用OverLoads关键字。比如,在上面的例子中,在没有遮蔽的情况下,如果要重载基类的Add方法,求两个整型值的和,需要添加下面的代码:
Public Overloads Function Add(ByVal a As Integer, ByVal b As Integer) As Integer
Return a+b
End Function
在有遮蔽的情况下重载基类的Add方法,也必须使用Shadows关键字。比如,2.2.5节的例子中,遮蔽基类的Add 方法以后,如果还要重载一个带有3个参数的Add方法,需要添加下面的代码:
Public Shadows Function Add(ByVal a As Single, ByVal b As Single.
ByVal c As Single) As Single
Return a+b+100
End Function
MyBase 和 base
在VB.NET 中使用MyBase或在VC#.NET 中使用base,可以在派生类中调用基类的成员。如2.2.5节遮蔽了基类的Add方法,在派生类中将不能应用该Add方法的定义。但是通过MyBase或base来调用时,仍然可以实现。比如,2.2.5节中遮蔽后重写的Add 方法可以重写为:
【VB.NET】
puoli Sadows Function Add(ByVal As Single, ByVal b As Single)As Single
Return MyBase.Add(a, b)+100
End Function
【VC#.NET】
public new float Add(float a,float b)
{
return base.Add(a,b)+100;
}