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. Mac上利用VScode配置c/c++开发环境

    Mac上利用VScode配置c/c++开发环境 哭辽,Typora里面最好不要插入表情,不然保存会闪退 首先你要有一个vscode 在扩展里面下载c/c++ 第一步 ⬆+com+p 打开命令模式:选择 ...

  2. 函数节流-歪说js

    歪谈js 起因: 夜深人静,月朗星稀.'window.onresize 事件' 与 '浏览器'在大战300回合,console.log('1') 1s 十次,然后就结束了,一个悲伤的故事. 事实证明太 ...

  3. Hibernate 和Mybatis的区别

    Hibernate 和Mybatis的区别   1.hibernate 入门门槛高,是一个标准的ORM框架(对象关系映射),不需要程序写sql,sql语句自动生成,对sql语句进行优化.修改比较困难. ...

  4. <软件工程基础>

    我是JX_Z,学习信息安全方向 //(怎么在这头不头尾不尾的地方弄个自我介绍这么尴尬呢) 之前也写过一些随笔记录自己的学习过程 软件工程基础课程中遇到的问题和学习心得都会记录在这篇文章中不断更新. 谢 ...

  5. android获取系统信息

    连接手机,adb shell 进入 Android Shell 模式,输入 getprop 获取系统属性值 通过上面方法拿到属性名,然后通过下面方法获取到系统的属性值 /** * 获取build.pr ...

  6. python常用的正则表达式,持续更新<<

    # -*- coding: utf-8 -*- import re str_0 = 'Aqin1012Heheheaaaaaaahehe如何da' def re_str(re_str_0,str_0) ...

  7. Pycharm常用快捷捷捷啊键= =

    超多快捷键的其实,懒得都记住(主要是记不住……) 这里记录一下自己觉得用了确实会很省事的,特别是当你没有鼠标的时候 = = 超常用的 Ctrl + /  注释该行 Ctrl + D  复制该行到下一行 ...

  8. 意外发现--http-server使用

    http-server 在很多情况下,需要在本地开启http服务器来测试.所以就需要一个简单的省事好用的http服务器.以前的时候,都是使用php的本地环境,但是,自从学了nodejs,发现了http ...

  9. 插件与App的跳转,及路由的关系

    在SDK中 无法直接跳App 的界面,这个时候需要使用 路由,或者通过 NSClassFromString 的 presentViewController 来跳转. 直接贴代码: UIViewCont ...

  10. HTTPSConnectionPool(host='files.pythonhosted.org', port=443): Read timed out的解决方法

    问题描述: Pycharm创建Django项目提示:HTTPSConnectionPool(host='files.pythonhosted.org', port=443): Read timed o ...