virtual关键字的本质是什么?
MSDN上对virtual方法的解释:试着翻译如下
当一个方法声明包含virtual修饰符,这个方法就是虚方法。如果没有virtual修饰符,那么就不是虚方法。
非虚方法的实现是不变的:不管该方法是被声明该方法的类的实例调用,还是该类的子类所调用,实现的结果都是一样。相比之下,虚方法会在子类中被取代。取代继承的虚方法的过程就是override。
在一个虚方法的调用中,方法所在的实例的运行时类型决定了实际哪个方法要被实现。在一个非虚方法的调用中,实例的编译时类型(编译时类型与运行时类型的区别)是决定性因素。准确地说,当一个参数列表为A,名叫N的方法在编译时类型C和运行时类型R的实例上调用时(R或者是C或者是C的子类),调用过程如下进行:
- 首先,重载决议应用在C,N和A上,去从方法集合中选择一个在C中声明并继承的特定的方法M。
- 然后,如果M是非虚方法,M就被调用。
- 否则,M是虚方法,R中M派生程度最大的方法的实现被调用。
对于声明或继承于一个类的每个虚方法,相对于那个类都有一个派生程度最大的实现。对于每一个类R,派生程度最大的虚方法实现如下定义:
- 如果R包含M的virtual声明,那么这就是M的派生程度最大的实现。
- 否则,如果R包含M的override,那么,这就是M的派生程度最大的实现。
- 否则,R的M派生程度最大的实现与R的直接父类的M最大派生程度最大实现相同。
using System;
class A
{
public void F() { Console.WriteLine("A.F"); }
public virtual void G() { Console.WriteLine("A.G"); }
}
class B: A
{
new public void F() { Console.WriteLine("B.F"); }
public override void G() { Console.WriteLine("B.G"); }
}
class Test
{
static void Main() {
B b = new B();
A a = b;
a.F();
b.F();
a.G();
b.G();
}
}
结果:
A.F
B.F
B.G
B.G
这么一大段,实在很难看懂。最后,根据《你必须知道的.NET》一书的对继承本质的描述,感觉清晰了一些。试着归纳如下:
对于给定的两个类:
class A
{
public virtual void F()
{
Console.WriteLine("A.F");
}
}
class B :A
{
public override void F()
{
Console.WriteLine("B.F");
}
}
以及程序:
A a=new B();
a.F();//"B.F"
到底发生了什么呢?
根据下图:

显然A是编译时类型,指向了作为运行时类型B的实例。
当new B()时,首先将B的父类A所有的虚方法都复制了一份,然后和B中自己所有的方法对比,如果B中有override的虚方法,则以子类的方法替换父类的方法,同时添加子类其他的新方法。
因此,结果是B中的方法被执行。
另一个例子:
class Z
{
public virtual void F1()
{
Console.WriteLine("Z.F");
}
}
class E:Z
{
public string type = "eType";
public override void F1()
{
Console.WriteLine(type);
}
}
class F : E
{
public string type = "fType";
public override void F1()
{
Console.WriteLine(type);
}
}
E e=new F();
Console.WriteLine(e.type);
e.F1();
结果出我意外:e.type里是eType,而e.F1()则是fType。
后者不难理解,因为是override,而前者是因为其为E类型,会首先访问离E类型创建最近的字段或方法。
最后,仍旧是MSDN上的一个例子
class A
{
public virtual void F() { Console.WriteLine("A.F"); }
}
class B : A
{
public override void F() { Console.WriteLine("B.F"); }
}
class C : B
{
new public virtual void F() { Console.WriteLine("C.F"); }
}
class D : C
{
public override void F() { Console.WriteLine("D.F"); }
}
D d = new D();
A a = d;
B b = d;
C c = d;
a.F();
b.F();
c.F();
d.F();
结果依次是:
B.F
B.F
D.F
D.F
两个D.F的结果好理解,为什么前两个是B.F呢?
因为d中其实有两个方法,一个是被隐藏的F(),其实现是B.F,另一个是显式的F(),其实现是D.F。根据”new则看类型,override只管新“,因为类型为A和B,那么其结果就为被隐藏的B.F。
virtual关键字的本质是什么?的更多相关文章
- C#基础知识五之abstract virtual关键字
abstract 用关键字abstract修饰的类叫做抽象类,且只能作为基类,也不能实例化. 用abstract定义的抽象类中不一定只包含抽象方法 ,可以包含非抽象方法. abstract定义的方法一 ...
- virtual 关键字
virtual 关键字用于修饰方法.属性.索引器或事件声明,并且允许在派生类中重写这些对象.例如,此方法可被任何继承它的类重写. public virtual double Area() { retu ...
- 浅析C#中new、override、virtual关键字的区别
Virtual : virtual 关键字用于修饰方法.属性.索引器或事件声明,并使它们可以在派生类中被重写. 默认情况下,方法是非虚拟的.不能重写非虚方法. virtual 修饰符不能与 stati ...
- 浅析c++中virtual关键字
http://blog.csdn.net/djh512/article/details/8973606 1.virtual关键字主要是什么作用? c++中的函数调用默认不适用动态绑定.要触发动态绑定, ...
- C++ 类的多态一(virtual关键字--构造函数深刻理解)
//virtual关键字--构造函数深刻理解 #include<iostream> using namespace std; /* C语言编译器,c++编译器全部是静态链编,就是一段一段代 ...
- C++ 学习笔记 (七)继承与多态 virtual关键字的使用场景
在上一篇 C++ 学习笔记 (六) 继承- 子类与父类有同名函数,变量 中说了当父类子类有同名函数时在外部调用时如果不加父类名则会默认调用子类的函数.C++有函数重写的功能需要添加virtual关键字 ...
- C++ 多态、虚函数(virtual 关键字)、静态联编、动态联编
函数重写:(在子类中重写父类中的函数) 父类中被重写的函数 依然会继承 给子类. 子类中重写的函数将覆盖父类中的函数. 通过作用域分辨符 :: 可以访问到父类中的函数. 例如: #includ ...
- 还原virtual函数的本质-----C++
当你每次看到C++类中声明一个virtual函数,特别是看到了一个virtual的虚构函数.你知道它的意思吗?你肯定会毫不犹豫的回答:不就是多态么...在运行时确定具体的行为么...完全正确,但这里我 ...
- virtual关键字
出于多态的考虑,为了覆盖, 子类同名覆盖函数(函数名.参数.返回值都相同) virtual void print(): 这也许会使人联想到函数的重载,但稍加对比就会发现两者是完全不同的:(1)重载的几 ...
随机推荐
- 【JAVA网络流之浏览器与服务器模拟】
一.模拟服务器获取浏览器请求http信息 代码: package p06.TCPTransferImitateServer.p01.ImitateServer; import java.io.IOEx ...
- LightOJ 1030 Discovering Gold(期望 概率)
正推,到达i的概率为p[i],要注意除了1和n外,到达i的概率并不一定为1 概率表达式为p[i] += p[j] / min(n - j, 6) 从j带过来的期望为exp[i] += exp[j] / ...
- Json转换利器Gson之实例一-简单对象转化和带泛型的List转化 (转)
Gson 是 Google 提供的用来在 Java 对象和 JSON 数据之间进行映射的 Java 类库.可以将一个 JSON 字符串转成一个 Java 对象,或者反过来. jar和源码下载地址: h ...
- AngularJS - 指令入门
指令,我将其理解为AngularJS操作HTML element的一种途径. 由于学习AngularJS的第一步就是写内置指令ng-app以指出该节点是应用的根节点,所以指令早已不陌生. 这篇日志简单 ...
- Dwz下拉菜单的二级联动
在DWZ文档中对组合框combox的是这样描述的: 在传统的select 用class 定义:class=”combox”, html 扩展:保留原有属性name, 增加了属性:ref. ref 属 ...
- POJ 1655 Balancing Act 树的重心
Balancing Act Description Consider a tree T with N (1 <= N <= 20,000) nodes numbered 1...N. ...
- Java 之 多线程编程
1.线程: a.由来:单任务OS -- 多任务OS b.进程:每一个进程对应一个应用程序,分配独立内存空间 c.线程:线程是进程内部的一个独立的执行分支 d.特点:共享内容地址空间,切换成本更低 2. ...
- Linux学习笔记(9)Linux常用命令之关机重启命令
(1)shutdown shutdown命令用于关机重启,其语法格式为: shutdown [选项] 时间 其中,-c选项表示取消前一个设置的shutdown命令,-h命令表示关机,-r命令表示重启 ...
- 如何给你的ASP.NET页面添加HelpPage
如何给你的ASP.NET页面添加HelpPage 最近写了一些webAPI,所以需要搞一套API的帮助文档,google了一下,发现这是可以自动生成的,以下就是如何自动生成HelpPage的说明. 参 ...
- HDU 5875 Function (2016年大连网络赛 H 线段树+gcd)
很简单的一个题的,结果后台数据有误,自己又太傻卡了3个小时... 题意:给你一串数a再给你一些区间(lef,rig),求出a[lef]%a[lef+1]...%a[rig] 题解:我们可以发现数字a对 ...