using System;
namespace Sample
{
// 演员(类)
class Actor
{
public void DoShow()
{
Console.WriteLine( " Doing a show... " );
}
}
// 乐手(类),继承自Actor类
class Bandsman : Actor
{
// 子类同名方法隐藏父类方法
// 其实标准写法应该是:
// public new void DoShow(){...}
// 为了突出"同名",我把new省了,编译器会自动识别
public void DoShow()
{
Console.WriteLine( " Playing musical instrument... " );
}
}
// 吉他手(类),继承自Bandsman类
class Guitarist : Bandsman
{
public new void DoShow()
{
Console.WriteLine( " Playing a guitar solo... " );
}
}
class Program
{
static void Main( string [] args)
{
// 正常声明
Actor actor = new Actor();
Bandsman bandsman = new Bandsman();
Guitarist guitarist = new Guitarist();
// 一般情况下,随着类的承继和方法的重写
// 方法是越来越具体、越来越个性化
actor.DoShow();
bandsman.DoShow();
guitarist.DoShow();
Console.WriteLine( " =========================== " );
// 尝试多态用法
Actor myActor1 = new Bandsman(); // 正确:乐手是演员
Actor myActor2 = new Guitarist(); // 正确:吉他手是演员
Bandsman myBandsman = new Guitarist(); // 正确:吉他手是乐手
// 仍然调用的是引用类型自身的方法,而非派生类的方法
myActor1.DoShow();
myActor2.DoShow();
myBandsman.DoShow();
}
}
}
代码分析:
using System;
using System.Collections.Generic;
using System.Text;
namespace Sample
{
// 演员(类)
class Actor
{
// 使用了virtual来修饰函数
// 此函数已经"名存实亡"了
public virtual void DoShow()
{
Console.WriteLine( " Doing a show... " );
}
}
// 乐手(类),继承自Actor类
class Bandsman : Actor
{
// 使用了override来修饰函数
// 此函数将取代(重写)父类中的同名函数
public override void DoShow()
{
Console.WriteLine( " Playing musical instrument... " );
}
}
// 吉他手(类),继承自Bandsman类
class Guitarist : Bandsman
{
public override void DoShow()
{
Console.WriteLine( " Playing a guitar solo... " );
}
}
class Program
{
static void Main( string [] args)
{
// 正常声明
Actor actor = new Actor();
Bandsman bandsman = new Bandsman();
Guitarist guitarist = new Guitarist();
// 一般情况下,随着类的承继和方法的重写
// 方法是越来越具体、越来越个性化
actor.DoShow();
bandsman.DoShow();
guitarist.DoShow();
Console.WriteLine( " =========================== " );
// 尝试多态用法
Actor myActor1 = new Bandsman(); // 正确:乐手是演员
Actor myActor2 = new Guitarist(); // 正确:吉他手是演员
Bandsman myBandsman = new Guitarist(); // 正确:吉他手是乐手
// Look!!!
// 调用的是引用类型所引用的实例的方法
// 引用类型本身的函数是virtual的
// 看似"存在",实际已经被其子类重写(不是隐藏,而是被kill掉了)
// 这正是virtual所要表达的"名存实亡"的本意,而非一个"虚"字所能传达
myActor1.DoShow();
myActor2.DoShow();
myBandsman.DoShow();
}
}
}
代码分析:
1. 除了将继承链中最顶层基类的DoShow()方法改为用virtual修饰;把继承链中派生类的DoShow()方法改为override修饰以重写基类的方法。
2. 主程序代码没变,但下半部分产生的效果完全不同!请体会"引用变量本身方法"与"引用变量所引用实例的方法"的不同--这是关键。
多态成因的分析:
为什么会产生这样的效果呢?这里要提到一个"virtual表"的问题。我们看看程序中继承链的构成:Actor à Bandsman à Guitarist。因为派生类不但继承了基类的代码(确切地说是public代码)而且还有自己的特有代码(无论是不是与基类同名,都是自己特有的)。从程序的逻辑视角来看,你可以这样想象:在内存中,子类的实例所占的内存块是在父类所占的内存块的基础上"追加"了一小块--拜托大家自己画画图。这多出来的一小块里,装的就是子类特有的数据和代码。
我们仔细分析这几句代码:
1. Actor actor = new Actor(); //常规的声明及分配内存方法
因为类是引用类型,所以actor这个引用变量是放在栈里的、类型是Actor类型,而它所引用的实例--同样也是Actor类型的--内存由new操作符来分配并且放在堆里。这样,引用变量与实例的类型一模一样、完全匹配。换句话说:栈里的引用变量所能"管理"的堆中的内存块大小正好、不多也不少。
2. Actor myActor1 = new Bandsman(); //正确:乐手是演员
同样是这句代码,在两个例子中产生的效果完全不同。为什么呢?且看!在例1中,在Bandsman类中只是使用new将父类的DoShow()给隐藏了--所起的作用仅限于自己对父类追加的代码块中,丝毫没有影响到父类。而栈中的引用变量是Actor类型的myActor1,它只能管理Actor类实例所占的那么大一块内存,而对追加的内存毫无控制能力(或者说看不见追加的这块内存)。因此,当你使用myActor1.DoShow();调用成员方法时,myActor1只能使唤自己能管到的那块内存里的DoShow()方法。那么例2中呢?难道例2中的myActor1就能管理追加的一块内存了吗?否也!它仍然管理不了,但不要忘了--这时候Actor类中的DoShow()方法已经被virtual所修饰,同时Bandsman类中的DoShow()方法已经被override修饰。这时候,当执行myActor1.DoShow();一句时,myActor1调用自己所管辖的内存块时,发现DoShow()这个函数已经标记为"可被重写"了(其实,在VB.NET中,与C#的virtual关键字对应的关键字就是Overridable,更直白),那么它就会尝试去发现有没有override链(也就是virtual表,即"虚表")的存在,如果存在,那么就调用override链上的最新可用版本--这就有了我们在例2中看到的效果。
3. Actor myActor2 = new Guitarist(); //正确:吉他手是演员
通过这句代码,你也可以想象一下2级重写是怎么形成的,同时也可以感悟一下所谓"重写链上最新的可用版本"是什么意思。
4. Guitarist myActor2 = new Actor(); //错误:想一想为什么?
呵呵,这是错误的,原因是引用变量所管理的内存大小超出了实例实际的内存大小。