C# virtual 是虚拟的含义,在 C# 语言中,默认情况下类中的成员都是非虚拟的,通常将类中的成员定义成虚拟的,表示这些成员将会在继承后重写其中的内容。

virtual 关键字能修饰方法、属性、索引器以及事件等,用到父类的成员中。

使用 virtual 关键字修饰属性和方法的语法形式如下。

//修饰属性
public  virtual  数据类型  属性名{get; set; }

//修饰方法
访问修饰符  virtual  返回值类型方法名
{
    语句块;
}

需要注意的是,virtual 关键字不能修饰使用 static 修饰的成员。

此外,virtual 关键字既可以添加到访问修饰符的后面,也可以添加到访问修饰符的前面,但实际应用中习惯将该关键字放到访问修饰符的后面。

子类继承父类后能重写父类中的成员,重写的关键字是 override。

所谓重写是指子类和父类的成员定义一致,仅在子类中增加了 override 关键字修饰成员。

例如在父类中有一个求长方形面积的方法,方法定义如下。

  1. publie int Area(int x, int y)
  2. {
  3. return x * y
  4. }

在子类中重写该方法的代码如下。

public override int Area(int x,int y)
{
    语句块;
    return  整数类型的值;
}

在子类中重写父类中的方法后能改变方法体中的内容,但是方法的定义不能改变。

【实例 1】将上一节《C# base》中定义的 Person 类中的 Print 方法更改为虚拟的方法,分别用 Student 类和 Teacher 类继承 Person 类,并重写 Print 方法,打印出学生信息和教师信息。

为了减少重复的代码,在每个类中省略了属性部分的定义内容,仅保留 Print 方法部分的内容,实现的代码如下。

  1. class Person
  2. {
  3. public virtual void Print()
  4. {
  5. Console.WriteLine("编号:"+ Id);
  6. Console.WriteLine("姓名:"+ Name);
  7. Console.WriteLine("性别:"+ Sex);
  8. Console.WriteLine("身份证号:"+ Cardid);
  9. Console.WriteLine("联系方式:"+ Tel);
  10. }
  11. }
  12. class Student:Person
  13. {
  14. public override void Print()
  15. {
  16. Console.WriteLine("编号:"+ Id);
  17. Console.WriteLine("姓名:"+ Name);
  18. Console.WriteLine("性别:"+ Sex);
  19. Console.WriteLine("身份证号:"+ Cardid);
  20. Console.WriteLine("联系方式:"+ Tel);
  21. Console.WriteLine("专业:"+ Major);
  22. Console.WriteLine("年级:"+ Grade);
  23. }
  24. }
  25. class Teacher:Person
  26. {
  27. public override void Print()
  28. {
  29. Console.WriteLine("编号:"+ Id);
  30. Console.WriteLine("姓名:"+ Name);
  31. Console.WriteLine("性别:"+ Sex);
  32. Console.WriteLine("身份证号:"+ Cardid);
  33. Console.WriteLine("联系方式:"+ Tel);
  34. Console.WriteLine("专业:"+ Major);
  35. Console.WriteLine("年级:"+ Grade);
  36. }
  37. }

通过上面的代码即可完成对 Person 类中 Print 方法的重写,在重写后的 Print 方法中能直接调用在 Person 类中定义的成员。

但读者会发现在 Person 类的 Print 中已经对 Person 中的相关属性编写了输出操作的代码,而每一个子类中又重复地编写了代码,造成代码的冗余,也没有体现出代码重用的特点。

如果能在重写父类方法的同时直接使用父类中已经编写过的内容就会方便很多。

在重写 Print 方法后仍然需要使用 base 关键字调用父类中的 Print 方法执行相应的操作。

【实例 2】改写实例 1 中的 Student 和 Teacher 类中重写的 Print 方法,使用 base 关键字调用父类中的 Print 方法。

根据题目要求,更改后的代码如下。

  1. class Student:Person
  2. {
  3. public override void Print()
  4. {
  5. base.Print ();
  6. Console.WriteLine("专业:"+ Major);
  7. Console.WriteLine("年级:"+ Grade);
  8. }
  9. }
  10. class Teacher:Person
  11. {
  12. public override void Print()
  13. {
  14. base.Print ();
  15. Console.WriteLine("专业:"+ Major);
  16. Console.WriteLine("年级:"+ Grade);
  17. }
  18. }

从上面的代码可以看出继承给程序带来的好处,不仅减少了代码的冗余,还增强了程序的可读性。

方法隐藏和重写方法有区别吗?这是很多初学者常问的问题。观察以下代码,思考结果会是什么?

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. A a1 = new B();
  6. a1.Print();
  7. A a2 = new C();
  8. a2.Print();
  9. }
  10. }
  11. class A
  12. {
  13. public virtual void Print()
  14. {
  15. Console.WriteLine("A");
  16. }
  17. }
  18. class B :A
  19. {
  20. public new void Print()
  21. {
  22. Console.WriteLine("B");
  23. }
  24. }
  25. class C :A
  26. {
  27. public override void Print()
  28. {
  29. Console.WriteLine("C");
  30. }
  31. }

执行上面的代码,效果如下图所示。

从上面的执行效果可以看出,使用方法隐藏的方法调用的结果是父类 A 中 Print 方法中的内容,而使用方法重写的方法调用的结果是子类 C 中 Print 方法中的内容。

因此方法隐藏相当于在子类中定义新方法,而方法重写则是重新定义父类中方法的内容。

从上面的代码也可以看出,在“A a1=new B()”语句中 A 类是父类、B 类是子类,相当于将子类转换成父类,即隐式转换。

如果需要将父类转换成子类,则需要强制转换,并且在强制转换前需要先将所需的子类转换成父类,示例代码如下。

  1. A a2=new C();
  2. C c=(C) a2;
  3. c.Print();

在上面的实例中,a2 是父类对象,然后将其强制转换成 C 类对象。

Object 类中的 ToString 方法能被类重写,并返回所需的字符串,通常将其用到类中返回类中属性的值。

在 Student 类中添加重写的 ToString 方法,代码如下。

  1. class Student
  2. {
  3. public string Major{ get; set;}
  4. public string Grade{ get; set;}
  5. public void Print()
  6. {
  7. Console.WriteLine("专业:"+ Major);
  8. Console.WriteLine("年级:"+ Grade);
  9. }
  10. public override string ToString()
  11. {
  12. return Major+","+Grade;
  13. }
  14. }

这样,在调用 Student 类中的 ToString 方法时即可获取专业和年级的值。

此外,除了 ToString 方法,在类中也可以重写 Equals 方法、GetHashCode 方法。

C# virtual 函数的更多相关文章

  1. 09——绝不在构造和析构函数中调用virtual函数

    在base class构造期间,virtual函数不是virtual函数. 构造函数.析构函数中不要调用virtual函数.

  2. 考虑virtual函数以外的选择

    在C++中,有四种选择可以替代virtual函数的功能: 1.non-virtual interface(NVI)手法,这是一种template method模式.它以public non-virtu ...

  3. Effective C++ -----条款35:考虑virtual函数以外的其他选择

    virtual函数的替代方案包括NVI手法及Strategy设计模式的多种手法.NVI手法自身是一个特殊形式的Template Method设计模式. 将机能从成员函数移到class外部函数,带来的一 ...

  4. Effective C++ -----条款09:绝不在构造和析构过程中调用virtual函数

    在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层).

  5. 为什么内联函数,构造函数,静态成员函数不能为virtual函数

    http://blog.csdn.net/freeboy1015/article/details/7635012 为什么内联函数,构造函数,静态成员函数不能为virtual函数? 1> 内联函数 ...

  6. 条款9:不要在构造和析构过程中调用virtual函数

    如下是一个股票交易的例子: class Transaction // 交易的基类 { public: Transaction(); ; // 用于记录交易日志 }; Transaction::Tran ...

  7. effective c++:virtual函数的替代方案

    绝不重新定义继承来的缺省值 首先明确下,虚函数是动态绑定,缺省参数值是静态绑定 // a class for geometric shapes class Shape { public: enum S ...

  8. effective c++:virtual函数在构造函数和析构函数中的注意事项

    如不使用自动生成函数要明确拒绝 对于一个类,如果你没有声明,c++会自动生成一个构造函数,一个析构函数,一个copy构造函数和一个copy assignment操作符. class Empty { p ...

  9. 考虑virtual函数以外的其它选择

    详情见<Effective C++>item35 1.使用non-virtual interface(NVI)手法,这是Template Method设计模式的一种特殊形式. 它以publ ...

  10. 还原virtual函数的本质-----C++

    当你每次看到C++类中声明一个virtual函数,特别是看到了一个virtual的虚构函数.你知道它的意思吗?你肯定会毫不犹豫的回答:不就是多态么...在运行时确定具体的行为么...完全正确,但这里我 ...

随机推荐

  1. JavaDay3(上)

    Java learning_Day3(上) 本人学习视频用的是马士兵的,也在这里献上 <链接:https://pan.baidu.com/s/1qKNGJNh0GgvlJnitTJGqgA> ...

  2. [USACO09JAN]Total Flow【网络流】

    Farmer John always wants his cows to have enough water and thus has made a map of the N (1 <= N & ...

  3. PIE-SDK For C++栅格数据集的读取

    1.功能简介 栅格数据包含很多信息,在数据的运用中需要对数据的信息进行读取,目前PIE SDK支持多种数据格式的数据读取,下面对栅格数据格式的数据读取功能进行介绍. 2.功能实现说明 2.1 实现思路 ...

  4. const真有点烦

    在C++中const代替#define的操作,当定义一个const时必须附一个值给它:const int size = 100;通常C++编译器不为const创建存储空间,相反它把这个定义保存在符号表 ...

  5. Oracle监听出现的问题总结,以及解决办法

    包括的错误类型: 1.ORA-12541: TNS: 无监听程序 2. ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务 3.ORA-12560: TNS: 协议适配器错误 ...

  6. python3练习100题——033

    链接:http://www.runoob.com/python/python-exercise-example33.html 题目:按逗号分隔列表. l=[1,2,3,4] k=1 for i in ...

  7. JUC-JUC强大的辅助类讲解(Semaphore、CyclicBarrier、CountDownLatch)

    一.CountDownLatch 减少计数 1.原理 * CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞. * 其它线程调用countDown方法会将 ...

  8. ubuntu 报错 Unable to locate package

    解决方案参考 https://blog.csdn.net/u010622613/article/details/83017163

  9. C++-POJ2975-Nim

    题目把Nim游戏为什么可以取异或和讲解得十分清楚,建议多读几次,理解一下 再一个,可以把每次异或视为一次取数,因此(k[i]^sg)<k[i]即为一种可行操作 /* Nim is a 2-pla ...

  10. linux学习笔记1:linux驱动设备概述