比起前面的封装和继承,多态这个概念不是那么好理解。我们还是从一个事例开始:

公司最近为了陶冶情操,养了几种动物(Animal),有猫(Cat)、狗(Dog)、羊(Sheep),这些动物都有共同的特性,会吃(Eat)、会叫(Shout),但是它们吃的不同,叫的也不同。既然这样,我们能不能设计一个动物类(Animal)和它的成员(Eat方法、Shout方法)来表示这些动物的共同特征,而当我们关注猫时,猫来实现这两个成员(吃鱼、喵喵叫);当我们关注狗时,狗来实现这两个成员(吃肉和汪汪叫)。

1.什么是多态

上述例子就是一个典型的多态,就是父类的一些成员,子类继承后去重写从而实现不同的功能。

多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。这就是多态,这种特性称为多态性。

2.多态的分类

多态性分为两种,一种是编译时的多态性,一种是运行时的多态性。

编译时的多态性(重载):编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。

运行时的多态性(重写):运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中运行时的多态性是通过覆写虚成员实现

3.多态的实现

我们知道多态有两种,一种是编译时通过重载实现,另一种是运行时,通过重写或叫覆写来实现,那么如何实现他们?

3.1编译时多态:重载(overload)

重载(overload):重载指的是同一个类中有两个或多个名字相同但是参数(参数签名)不同的方法,(注:返回值不能区别函数是否重载),重载没有关键字

注意:

A.从重载的定义来看,重载是一种编译时多态

B.重载不需要事先定义可重载的方法,即没有关键字

C.重载只是针对一个类内部的几个参数不同,名称相同的方法。

我们还是用那几只陶冶情操的动物来示例说明,代码如下:

 /// <summary>
/// 狗(多态:重载事例)
/// </summary>
class Dog
{
/// <summary>
/// 叫
/// </summary>
public void Shout()
{
Console.WriteLine("汪!");
} /// <summary>
/// 叫(重载方法)
/// </summary>
public void Shout(int count)
{
int i = ;
string shout = "";
do
{
shout += "汪!";
i++;
} while (i <= count);
Console.WriteLine(shout);
}
}
/调用
Dog dog = new Dog();
dog.Shout();
dog.Shout();

3.2运行时多态:重写

重写有两种,一种是override修饰符,另一种使用new修饰符,下面会举例说明两种重写的使用方法和异同。

重写(override):也称过载,重写是指子类对父类中虚函数或抽象函数的“覆盖”(这也就是有些书将过载翻译为覆盖的原因),但是这种“覆盖”和用new关键字来覆盖是有区别的。

       /// <summary>
/// 动物类(父类)
/// </summary>
class Animal
{
/// <summary>
/// 名字
/// 说明:类和子类可访问
/// </summary>
protected string name; /// <summary>
/// 构造函数
/// </summary>
/// <param name="name"></param>
public Animal(string name)
{
this.name=name;
} /// <summary>
/// 名字(虚属性)
/// </summary>
public virtual string MyName
{
get { return this.name; } } /// <summary>
/// 吃(虚方法)
/// </summary>
public virtual void Eat()
{
Console.WriteLine("我会吃!");
} /// <summary>
/// 叫(虚方法)
/// </summary>
public virtual void Shout()
{
Console.WriteLine("我会叫!");
}
} /// <summary>
/// 狗(子类)
/// </summary>
class Dog:Animal
{
string myName;
public Dog(string name): base(name)
{
myName = name;
} /// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:狗狗,我叫:"+this.name; } } /// <summary>
/// 吃(重写父类虚方法)
/// </summary>
public override void Eat()
{
Console.WriteLine("我喜欢吃肉!");
} /// <summary>
/// 叫(重写父类方法)
/// </summary>
public override void Shout()
{
Console.WriteLine("汪!汪!汪!");
}
}
//调用方法
Animal dog = new Dog("旺财");
string myName=dog.MyName;
Console.WriteLine(myName);
dog.Eat();
dog.Shout();
//运行结果如下:
我是:狗狗,我叫:旺财
我喜欢吃肉!
汪!汪!汪!

重写(new)

new:覆盖指的是不同类中(基类或派生类)有两个或多个返回类型、方法名、参数都相同,但是方法体不同的方法。但是这种覆盖是一种表面上的覆盖,所以也叫隐藏,被覆盖的父类方法是可以调用得到的。

     /// <summary>
/// 动物类(父类)
/// </summary>
class Animal
{
/// <summary>
/// 名字
/// 说明:类和子类可访问
/// </summary>
protected string name; /// <summary>
/// 构造函数
/// </summary>
/// <param name="name"></param>
public Animal(string name)
{
this.name=name;
} /// <summary>
/// 名字(虚属性)
/// </summary>
public virtual string MyName
{
get { return this.name; } } /// <summary>
/// 吃(虚方法)
/// </summary>
public virtual void Eat()
{
Console.WriteLine("我会吃!");
} /// <summary>
/// 叫(虚方法)
/// </summary>
public virtual void Shout()
{
Console.WriteLine("我会叫!");
}
} /// <summary>
/// 狗(子类)
/// </summary>
class Dog:Animal
{
string myName;
public Dog(string name): base(name)
{
myName = name;
}
/// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:狗狗,我叫:"+this.name; }
} /// <summary>
/// 吃(重写父类虚方法)
/// </summary>
new public void Eat()
{
Console.WriteLine("我喜欢吃肉!");
} /// <summary>
/// 叫(重写父类方法)
/// </summary>
public new void Shout()
{
Console.WriteLine("汪!汪!汪!");
}
}
//调用方法 使用new重写,则只调用父类的方法
Animal dog = new Dog("旺财");
string myName=dog.MyName;
Console.WriteLine(myName);
dog.Eat();
dog.Shout(); //执行结果如下:
我是:狗狗,我叫:旺财
我会吃!
我会叫!

如下改一下调用方法:

//调用方法
Dog dog = new Dog("旺财");
string myName=dog.MyName;
Console.WriteLine(myName);
dog.Eat();
dog.Shout(); //执行结果如下:
我是:狗狗,我叫:旺财!
我爱吃肉!
汪!汪!汪!

可以看出,当派生类Dog的Eat()方法使用new修饰时,Dog的对象转换为Animal对象后,调用的是Animal类中的Eat()方法。其实可以理解为,使用new关键字后,使得Dog中的Eat()方法和Animal中的Eat()方法成为毫不相关的两个方法,只是它们的名字碰巧相同而已。所以, Animal类中的Eat()方法不管用还是不用virtual修饰,也不管访问权限如何,或者是没有,都不会对Dog的Eat()方法产生什么影响(只是因为使用了new关键字,如果Dog类没用从Animal类继承Eat()方法,编译器会输出警告)。

我想这是设计者有意这么设计的,因为有时候我们就是要达到这种效果。严格的说,不能说通过使用new来实现多态,只能说在某些特定的时候碰巧实现了多态的效果。

3.3 要点:

a.多态是面向对象的重要特性之一,指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

b.多态分为两种:一种是编译时多态,使用重载实现;另一种是运行时多态,使用重写实现;

c.重写有两种,一种使用override关键词,另一种使用new关键词

d.new重写实际上是对父类方法的隐藏,被覆盖的父类方法可以调用得到。因此new可以重写(或说是隐藏)的父类方法不一定要定义为虚方法或抽象方法。只是如果父类方法是虚方法或抽象方法时会覆盖父类方法,如果不是,则隐藏。

e.重载和覆盖的发生条件
重载,必然发生在一个类中,函数名相同,参数类型或者顺序不同构成重载,与返回类型无关
重写,必然发生在基类和派生类中,其类函数用virtual修饰,派生类用override修饰
覆盖,在子类中写一个和基类一样名字(参数不同也算)的非虚函数,会让基类中的函数被隐藏,编译后会提示要求使用New关键字 new 修饰

隐藏,在子类中可以通过new 隐藏父类的方法

f.new覆盖与重写、重载的区别:

当子类与父类的参数不同时

当基类函数不是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载)

当基类函数是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载;因为参数不同,所以不是重写)

当子类与父类的参数相同时

当基类函数不是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载,因为基类不是虚函数,所以是隐藏不是重写)

当基类函数是虚函数时,基类函数将被覆盖。(因为子类和基类不在同一范围内,所以不是重载)

最新文章

  1. Xcode 生成静态库相关设置:
  2. 解决JSP页面获取的数据库数据乱码问题
  3. scss编译
  4. 专题实验 PGA
  5. Ubuntu无法进入图形界面及VirtualBox扩容的解决方案
  6. C# Coding Conventions(译)
  7. 提高测试脚本复用性降低DOM结构引起路径变化的影响
  8. 前端神器-神级代码编辑软件Sublime Text下载、使用教程、插件推荐说明、全套快捷键
  9. 15个HTML元素方法!
  10. k8s对接ceph存储
  11. springboot 格式化返回日期
  12. Jq相关常用操作
  13. flask 初学1
  14. topcoder srm 505 div1
  15. ASP.NET MVC加载ASCX之后,并为之赋值
  16. Windows环境下ELK(5.X)平台的搭建
  17. 利用python计算windows全盘文件md5值的脚本
  18. LINUX网络编程 IO 复用
  19. 在ASP.NET Core 2.x中获取客户端IP地址
  20. 同一个String在使用不同的charset编码的时候equals仍然是返回true吗

热门文章

  1. sqoop导入增量数据
  2. css sprite技巧详解
  3. 用python实现的抓取腾讯视频所有电影的爬虫
  4. KISSY(JS)炫动导航,缓动应用实例(^_^)
  5. angularjs file upload插件使用总结
  6. php设计模式课程---2、为什么会用到简单工厂设计模式
  7. spring boot: 一般注入说明(四) Profile配置,Environment环境配置 @Profile注解
  8. eclipse导入jar包连接mysql
  9. WCF之契约的分类(部分為參考他人)
  10. Linux-shell 练习题(一)