C# 基础复习之虚函数(virtual)示例
学习自
C# 虚函数
观前提示:
- 示例代码是我从程序中扒出来的,可以运行,但不能复制粘贴就直接运行。需要改一下命名空间之类的。主要是用来帮助解释代码运行过程。
virtualTest.vt1A b = new virtualTest.vt1B();
其中前面的类virtualTest.vt1A
在下文被称为声明类
;后面的virtualTest.vt1B()
被称为实例类
。
基础示例
public class vt1A
{
public virtual void Fun() /* 使用修饰符virtual,表明这是一个虚函数 */
{
Console.WriteLine("Fun in Class vt1A");
}
public void AAA()
{
}
}
public class vt1B: vt1A
{
public override void Fun()/* 使用修饰符override重写父类的虚函数 */
{
Console.WriteLine("Fun in Class vt1B");
}
// public void AAA() {}
// public override void AAA() {}
// 报错,C#是不能随便在子类重写父类方法的。
}
public class vt1C : vt1B
{
/* 有继承关系,但没有重写虚函数 */
}
public class vt1D : vt1A
{
public new void Fun()/* 使用修饰符new创建函数,表明这是一个与父类函数同名的子类独立函数。并不是虚函数的重写 */
{
Console.WriteLine("Fun in Class vt1D");
}
}
///
/// virtual修饰符,基础验证
///
private static void virtualTestNormal()
{
Console.WriteLine("virtualTestNormal Start");
/*
* virtual修饰符基础知识
* 用virtual修饰的函数表示为虚函数。
* 类中的所有函数默认不是虚函数,只有用virtual修饰的才是虚函数。
* 使用虚函数的注意事项:
* 调用一个对象的虚函数时,系统首先检查该对象的声明类。判断调用的函数是否是虚函数。
* 如果不是虚函数,就立刻执行函数。如果是虚函数,系统就不会执行该函数,而是去检查该对象的实例类。
* 系统会检查该实例类中是否重写(override)该虚函数。
* 如果实例类中重写了虚函数,那么立刻执行实例类中重写的函数。
* 如果实例类没有重写该虚函数,系统就会去检查该实例类的父类。检查该实例类的父类有没有重写该虚函数。
* 如果重写了,立刻执行。没重写,继续寻找父类的父类。
* 直到找到第一个重写了该虚函数的类为止(也有可能找到最初的基类,执行的就是基类中virtual修饰的函数)。
*/
virtualTest.vt1A a = new virtualTest.vt1A();
virtualTest.vt1A b = new virtualTest.vt1B();
virtualTest.vt1A c = new virtualTest.vt1C();
virtualTest.vt1A d = new virtualTest.vt1D();
a.Fun();
// 输出结果:Fun in Class vt1A。
// 1.先检查声明类vt1A,发现是virtual修饰的虚方法。
// 2.寻找它的实例类,还是vt1A。
// 3.执行vt1A中的方法
b.Fun();
// 输出结果:Fun in Class vt1B。
// 1.先检查声明类vt1A,发现是virtual修饰的虚方法。
// 2.寻找它的实例类,也就是vt1B。
// 3.执行vt1B中的方法
c.Fun();
// 输出结果:Fun in Class vt1B。
// 1.先检查声明类vt1A,发现是virtual修饰的虚方法。
// 2.寻找它的实例类,也就是vt1C。
// 3.vt1C中没有重写Fun方法(也就是没有override Fun)。
// 4.寻找vt1C的父类,也就是vt1B,检查vt1B中有没有重写Fun
// 5.vt1B中发现了重写Fun,执行。否则继续向vt1B的父类寻找
d.Fun();
// 输出结果:Fun in Class vt1A。
// 1.先检查声明类vt1A,发现是virtual修饰的虚方法。
// 2.寻找它的实例类,也就是vt1D。
// 3.vt1D中虽然有Fun方法,但没有用override修饰,反而使用了new。说明该方法是与其父类同名的独立方法。所以还是没有重写
// 4.寻找vt1D的父类,就是声明类vt1A本身,找到Fun方法,执行
// 如何执行vt1D中使用new修饰的Fun方法?
// 直接声明类型为vt1D
virtualTest.vt1D newd = new virtualTest.vt1D();
newd.Fun();
Console.WriteLine("End virtualTestNormal");
}
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
virtualTestNormal();
Console.ReadKey();
}
几个问题
1.virtual修饰的虚函数必须在子类重写吗?
答:不必须,可以不重写,完全不理会。
2.子类中override重写的函数就肯定是虚函数吗?
答:也不一定,还有可能是abstract
3.重写虚函数必须用override吗?
答:是的。C#重写虚函数必须virtual与override配套使用。否则你不能在子类中定义与父类名称相同的函数。除非你用new修饰子类中的同名方法。当然new就代表这是子类中的一个独立新函数,与父类中的虚函数就没有关系了。不存在所谓重写的关系。
4.virtual与abstract的区别?
答:其实他们区别还是很大的,只不过都涉及重写会容易让人混淆。几个重点。①虚函数必须有实现(也就是{ }),抽象函数必须没有实现。②虚函数被不被子类重写都可以,抽象函数必须被子类重写;③virtual只能修饰函数不能修饰类,abstract既能修饰函数也能修饰类。
进阶示例
class Base
{
public Base()
{
PrintFields();
}
public virtual void PrintFields()
{
}
}
class vt2A: Base
{
public vt2A()
{
PrintFields();
}
public vt2A(string context)
{
Console.WriteLine("para:" + context);
PrintFields();
}
public override void PrintFields()
{
Console.WriteLine("sssss");
}
}
class vt2B : vt2A
{
int x = 1;
int y;// int y 默认为0;
public vt2B()
{
y = -1;
}
public override void PrintFields()
{
Console.WriteLine("x={0},y={1}", x, y);
}
}
class vt2B1 : vt2A
{
int x = 1;
int y;// int y 默认为0;
public vt2B1()
{
y = -1;
}
}
private static void virtualTestAdvanced()
{
Console.WriteLine("virtualTestAdvanced Start");
new virtualTest.vt2B();
// 输出结果:x=1,y=0 ; x=1,y=0 输出两遍
// 1.创建子类vt2B的对象,先找到其父类vt2A。
// 2.然而vt2A也有父类,所以继续向上找,Base,是一个基类,开始调用构造函数。
// 3.调用Base的构造函数,执行其中的函数PrintFields();
// 4.然而PrintFields()是一个虚函数,所以首先要查看实例类vt2B中有没有重写,
// 5.找到了vt2B中的重写函数,执行,输出一遍x=1,y=0。
// 6.之后再接着调用vt2A中的构造函数,也是PrintFields()
// 7.重复刚才的虚函数相关的判断,会再输出一个x=1,y=0。
// 8.然后执行vt2B的构造函数,完成对象创建。
new virtualTest.vt2B1();
// 输出结果:sssss ; sssss 输出两遍
// 所有过程与上述创建vt2B相同。
// 只不过检查实例类是否重写虚函数的时候,vt2B1类中没有重写虚函数,所以向上检查它的父类是否有重写虚函数。
// vt2B的父类vt2A中重写了虚函数,执行。
Console.WriteLine("End virtualTestAdvanced");
}
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
virtualTestAdvanced();
Console.ReadKey();
}
几个问题
1.这个进阶测试的难点
答:没有声明类。
2.没有声明类怎么办?
答:涉及知识点。创建子类对象时,会先调用父类的构造函数,然后才调用子类本身的构造函数。
3.如果父类有多个构造函数,我们有没有指定使用哪个怎么办?
答:该情况下,系统会自动隐式调用父类的无参构造函数。
4.如果创建该实例的类中没有重写虚函数怎么办?
答:虚函数的判断方式与以前一样,依次向上寻找它的父类中有没有重写就行。