父类中定义的虚函数
子类对象调用该函数的同名函数时,有三种:
(1)子类不定义同名函数,那么子类对象在调用此函数时,是调用了父类的该函数,即进入父类该函数的入口地址
(2)子类对象重写此函数(new或者override),那么子类对象在调用此函数时,是调用了子类自己的函数,至于父类该函数的入口地址是否还存在,之后讨论
(3)子类定义一个同名函数,那么子类对象在调用此函数时,是调用了子类自己的函数,隐藏父类该函数的入口地址(仍存在),这种情况跟new重写是一样的,换句话说new加和不加是没有影响的
那么对于第(2)种,new和override是有区别的,new是在父类函数的入口地址之外再创建一个同名函数入口地址,而override是把父类函数的入口地址覆盖了。
具体看我在vs2008中调试的例子代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class A
{
public virtual void Get()
{
Console.WriteLine("父类A函数");
}
}
class B:A
{
public new void Get()
{
Console.WriteLine("子类B函数");
}
}
class C : A
{
public override void Get()
{
Console.WriteLine("子类C函数");
}
}
class D : A
{
public void Get()
{
Console.WriteLine("子类D函数");
}
}
class Program
{
static void Main(string[] args)
{
/************
//父类对象调用父类方法
A a = new A();
a.Get();
Console.ReadLine();
//子类对象调用子类(new)方法
B b = new B();
b.Get();
Console.ReadLine();
//子类对象调用子类(override)方法
C c = new C();
c.Get();
Console.ReadLine();
//子类对象调用子类方法
D d = new D();
d.Get();
Console.WriteLine();
* /
/***************
//父类对象试图调用子类方法,需要将子类对象转化为父类对象,b调用的是父类的函数,c调用的是子类的函数。
//因为关键字new实现重写是再创建了一个函数入口地址,也就是说这时子类对象同时保存了父类该函数的入口地址和子类该函数的入口地址,
//只是父类该函数的入口地址被隐藏,但仍可用,当父类对象去调用去调用子类方法时,子类会提供父类对象该函数的入口地址。这样就实现了根据对象类型自动选择函数入口地址。(其实这里new加和不加效果是一样的)
//而关键字override实现重写是用子类创建的该函数入口地址覆盖父类该函数的入口地址,这样一来,子类对象只保存了子类该函数的入口地址。
a = b;
a.Get();
Console.WriteLine();
a = c;
a.Get();
Console.WriteLine();
a = d;
a.Get();
Console.WriteLine();
*/
/***************
//子类对象试图调用父类方法,需要将父类对象转化为子类对象。父类对象可以强制转换为子类对象,但是前提是此父类对象为子类实例化的结果。
//“父类对象为子类实例化的结果”与“父子类各自实例化一个对象后,将子类对象转化为父类对象”这两种情况都能实现
A a = new B();
a.Get();
Console.ReadLine();
((B)a).Get();
((A)a).Get();
a = new C();
a.Get();
Console.ReadLine();
* /
}
}
}
用override可以实现多态,用new可以实现更漂亮的多态,可以参考这篇文章http://www.cnblogs.com/kimma/archive/2008/09/07/1285857.html
总结虚函数作用,纯虚函数就是强制子类有同样的成员函数接口,虚函数就是强制子类有同样的成员函数接口以及接口默认实现。一个类中最好不要完全没有虚函数也不要全是虚函数,这都是设计不合理。总的来说,虚函数实现多态。