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. 851. spfa求最短路(spfa算法模板)

    给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数. 请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出impossible. 数据保证不存在负权回路. 输入格式 ...

  2. UVA122 二叉树的层次遍历

    题目链接 https://vjudge.net/problem/UVA-122 #include<bits/stdc++.h> using namespace std; #define l ...

  3. Python-Django学习笔记(一)-MTV设计模式

    Django是开源的.大而且全的Web应用框架. 它独具特色,采用了MTV设计模式. MTV框架包括:Model(模型).Template(模板)和View(视图) Model(模型):负责业务对象与 ...

  4. grep/sed/awk命令查看指定时间段的日志

    *grep命令 今天遇到研发要求查询定时任务(elastic-job)在14:00-14:40的日志,使用grep命令很方便: 命令: grep '时间' '日志文件名 ' 1.例如查询2020-02 ...

  5. 自定义Nginx返回页面

    1.403返回页面 #user nobody; worker_processes ; #error_log logs/error.log; #error_log logs/error.log noti ...

  6. 55.ORM外键:引用同app下的不同模型,引用不同app下的模型,引用模型自身使用详解

    外键和表关系 外键是属于数据库级别的,在MySQL中,表有两种引擎,一种是InnoDB,另外一种是myisam.如果使用的是InnoDB引擎,是支持外键约束的.外键的存在使得ORM框架在处理表关系的时 ...

  7. 单位px和em,rem的区别

    px 相对长度单位.像素(Pixel). 像素是相对于显示器屏幕分辨率而言的.例如,WONDOWS的用户所使用的分辨率一般是96像素/英寸.而MAC的用户所使用的分辨率一般是72像素/英寸.(css手 ...

  8. centos开发环境搭建

    1.检查是否安装php php -v yum install php 2.安装composer curl -sS https://getcomposer.org/installer |php //下载 ...

  9. SGD 讲解,梯度下降的做法,随机性。理解反向传播

    SGD 讲解,梯度下降的做法,随机性.理解反向传播 待办 Stochastic Gradient Descent 随机梯度下降没有用Random这个词,因为它不是完全的随机,而是服从一定的分布的,只是 ...

  10. os.getcwd()和os.path.realpath(__file__)的区别

    https://blog.csdn.net/xiaminli/article/details/74944580 python中split().os.path.split()函数用法