C#面向对象三大特性:多态
什么是多态
公司最近为了陶冶情操,养了几种动物(Animal),有猫(Cat)、狗(Dog)、羊(Sheep),这些动物都有共同的特性,会吃(Eat)、会叫(Shout),但是它们吃的不同,叫的也不同。既然这样,我们能不能设计一个动物类(Animal)和它的成员(Eat方法、Shout方法)来表示这些动物的共同特征,而当我们关注猫时,猫来实现这两个成员(吃鱼、喵喵叫);当我们关注狗时,狗来实现这两个成员(吃肉和汪汪叫)。
上述例子就是一个典型的多态,就是父类的一些成员,子类继承后去重写从而实现不同的功能。
定义:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。这就是多态,这种特性称为多态性。
多态的优缺点
好处:
简化了编程接口。它容许在类和类之间重用一些习惯性的命名,而不用为每一个新加的函数命名一个新名字。
简化代码。如果函数/方法参数中使用的是父类的类型,可以传入父类、子类的对象
局限性:
父类类型的变量,不能直接调用子类特有的方法。必须强转为子类类型的变量,才能直接调用子类特有的方法
注意:子类中如果重写了父类的方法(多态),那么父类中的这个方法将不会再调用。
多态的分类
多态性分为两种,一种是编译时的多态性,一种是运行时的多态性。
编译时的多态性(重载):编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。
运行时的多态性(重写):运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中运行时的多态性是通过覆写虚成员实现。
多态的实现
编译时多态:重载(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();
运行时多态:重写
重写有两种,一种是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>
public new 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来实现多态,只能说在某些特定的时候碰巧实现了多态的效果。
要点:
1. 多态是面向对象的重要特性之一,指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
2. 多态分为两种:一种是编译时多态,使用重载实现;另一种是运行时多态,使用重写实现;
3. 重写有两种,一种使用override关键词,另一种使用new关键词
4. new重写实际上是对父类方法的隐藏,被覆盖的父类方法可以调用得到。因此new可以重写(或说是隐藏)的父类方法不一定要定义为虚方法或抽象方法。只是如果父类方法是虚方法或抽象方法时会覆盖父类方法,如果不是,则隐藏。
5. 当用子类创建父类的时候,如C1 c = new C2(),重写会改变父类的功能,即调用子类的功能;而覆盖(new)不会,仍然调用父类功能
6. 重载和覆盖的发生条件:
重载,必然发生在一个类中,函数名相同,参数类型或者顺序不同构成重载,与返回类型无关
重写,必然发生在基类和派生类中,其类函数用virtual修饰,派生类用override修饰
覆盖,在子类中写一个和基类一样名字(参数不同也算)的非虚函数,会让基类中的函数被隐藏,编译后会提示要求使用New关键字 new 修饰隐藏,在子类中可以通过new 隐藏父类的方法
7. virtual修饰符不能与private、static、abstract或者override修饰符同时使用
8. override修饰符不能与new、static或者virtual修饰符同时使用,并且重写方法只能用于重写基类中的虚方法
f.new覆盖与重写、重载的区别:
当子类与父类的参数不同时
当基类函数不是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载)
当基类函数是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载;因为参数不同,所以不是重写)
当子类与父类的参数相同时
当基类函数不是虚函数时,基类函数将被隐藏。(因为子类和基类不在同一范围内,所以不是重载,因为基类不是虚函数,所以是隐藏不是重写)
当基类函数是虚函数时,基类函数将被覆盖。(因为子类和基类不在同一范围内,所以不是重载)
普通类多态Eg:
using System; namespace Traffic
{
class Animal
{
public virtual void Breed()//定义 public 修饰的 virtual 虚方法
{
Console.WriteLine("动物需要交配来繁殖后代");
}
protected virtual void Breed2()//定义 protected 修饰的 virtual 虚方法
{
Console.WriteLine("动物需要交配来繁殖后代");
}
public void DoBreed()
{
this.Breed2();
}
} class Mantis : Animal //继承自动物类的螳螂类
{
public override void Breed()//override重写父类Animal类型定义的繁殖方法
{
Console.WriteLine("母螳螂吃掉公螳螂繁殖后代");
} protected override void Breed2()//override重写父类Animal类型定义的繁殖方法
{
Console.WriteLine("母螳螂吃掉公螳螂繁殖后代");
}
} class Chook : Animal //继承自动物类的鸡类
{
public override void Breed()//override重写父类Animal类型定义的繁殖方法
{
Console.WriteLine("鸡生蛋来繁殖后代");
} protected override void Breed2()//override重写父类Animal类型定义的繁殖方法
{
Console.WriteLine("鸡生蛋来繁殖后代");
}
} class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();
animal.Breed(); Mantis mantis = new Mantis();//创建 mantis 对象
if (mantis is Animal)//is关键字判断对象 mantis 是否属于 Animal 类
{
Animal mantisAnimal = mantis as Animal;//as 关键字将 mantis 对象转换为 Animal 类型对象
mantisAnimal.Breed();//调用Animal类型对象的 Bread 方法
} Chook chook = new Chook();
if (chook is Animal)
{
Animal chookAnimal = chook as Animal;
chook.Breed();
}
Console.WriteLine(Environment.NewLine);//空行间隔 Animal animal2 = new Animal();
//animal2.DoBreed();
Breed(animal2);
Mantis mantis2 = new Mantis();
//mantis2.DoBreed();
Breed(mantis2);
Chook chook2 = new Chook();
chook2.DoBreed();
//Breed(chook2);
} static void Breed(Animal animal)//定义参数类型为父类型的 Breed 方法
{
animal.DoBreed();
}
}
}
代码分析:
1.定义 Mantis 和 Chook 类的时候重写了 Breed 方法
2.Animal mantisAnimal = mantis as Animal;//as 关键字将 mantis 对象转换为 Animal 类型对象,此时并未创建新的对象,只是将原来的对象转换了类型,mantis 和 mantisAnimal 指向同一个对象
3.代码示例是以两种不同的方式,一种是简单示例,一种是复杂示例
运行结果:
抽象类多态:
抽象类不能实例化,抽象方法只能在抽象类中定义,在定义抽象类的子类时必须将抽象类中定义的抽象方法重写。
抽象类多态Eg:
using System; namespace Traffic
{
abstract class Animal
{
public abstract void Breed();//定义 public 修饰的 abstract 抽象方法
protected abstract void Breed2();//定义 protected 修饰的 abstract 虚方法
public void DoBreed()
{
this.Breed2();
}
} class Mantis : Animal //继承自动物类的螳螂类
{
public override void Breed()//override重写父类Animal类型定义的繁殖方法
{
Console.WriteLine("母螳螂吃掉公螳螂繁殖后代");
} protected override void Breed2()//override重写父类Animal类型定义的繁殖方法
{
Console.WriteLine("母螳螂吃掉公螳螂繁殖后代");
}
} class Chook : Animal //继承自动物类的鸡类
{
public override void Breed()//override重写父类Animal类型定义的繁殖方法
{
Console.WriteLine("鸡生蛋来繁殖后代");
} protected override void Breed2()//override重写父类Animal类型定义的繁殖方法
{
Console.WriteLine("鸡生蛋来繁殖后代");
}
} class Program
{
static void Main(string[] args)
{
Mantis mantis = new Mantis();//创建 mantis 对象
if (mantis is Animal)//is关键字判断对象 mantis 是否属于 Animal 类
{
Animal mantisAnimal = mantis as Animal;//as 关键字将 mantis 对象转换为 Animal 类型对象
mantisAnimal.Breed();//调用Animal类型对象的 Bread 方法
} Chook chook = new Chook();
if (chook is Animal)
{
Animal chookAnimal = chook as Animal;
chook.Breed();
}
Console.WriteLine(Environment.NewLine);//空行间隔 Mantis mantis2 = new Mantis();
//mantis2.DoBreed();
Breed(mantis2);
Chook chook2 = new Chook();
chook2.DoBreed();
//Breed(chook2);
} static void Breed(Animal animal)//定义参数类型为父类型的 Breed 方法
{
animal.DoBreed();
}
}
}
运行结果:
接口多态Eg:
using System; namespace Traffic
{
interface IAnimal
{
void Breed();//声明接口成员
} class Mantis : IAnimal //继承自接口的螳螂类
{
public void Breed()//类继承接口后,必须将接口声明的全部成员都实现
{
Console.WriteLine("母螳螂吃掉公螳螂繁殖后代");
}
} class Chook : IAnimal //继承自接口的鸡类
{
public void Breed()
{
Console.WriteLine("鸡生蛋来繁殖后代");
}
} class Program
{
static void Main(string[] args)
{
Mantis mantis = new Mantis();//创建 mantis 对象
if (mantis is IAnimal)//is关键字判断对象 mantis 是否属于 Animal 类
{
IAnimal imantis = mantis as IAnimal;//as 关键字将 mantis 对象转换为 Animal 类型对象
imantis.Breed();//调用Animal类型对象的 Bread 方法
} IAnimal ichook = new Chook();//创建鸡对象后直接转换为 IAnimal 接口类型变量 ichook
ichook.Breed();
Console.WriteLine(Environment.NewLine);//空行间隔 Mantis mantis2 = new Mantis();
Breed(mantis2);
Chook chook2 = new Chook();
Breed(chook2); Console.ReadLine();
} static void Breed(IAnimal animal)//定义参数类型为父类型的 Breed 方法
{
animal.Breed();
}
}
}
运行结果:
原文链接:https://www.cnblogs.com/qinyi173/p/4685947.html
原文链接:http://blog.sina.com.cn/s/blog_bc7f750001016gg4.html
C#面向对象三大特性:多态的更多相关文章
- [.net 面向对象编程基础] (13) 面向对象三大特性——多态
[.net 面向对象编程基础] (13) 面向对象三大特性——多态 前面两节,我们了解了面向对象的的封装和继承特性,面向对象还有一大特性就是多态.比起前面的封装和继承,多态这个概念不是那么好理解.我们 ...
- Python()- 面向对象三大特性----多态
多态: python 生来支持多态白话:一种事物的多种形态 (动物可以继承给狗,也可以继承给猫) class Animal: pass class Dog(Animal): def attack(se ...
- Python入门-面向对象三大特性-多态
Pyhon不支持多态并且也用不到多态,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚"鸭子类型".
- python面向对象三大特性-多态
import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod ...
- JAVA基础——面向对象三大特性:封装、继承、多态
JAVA面向对象三大特性详解 一.封装 1.概念: 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问. 2.好处: 只能通过规定的方法访问数据. ...
- python 面向对象三大特性(封装 多态 继承)
今天我们来学习一种新的编程方式:面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)注:Java和C#来说只支持面向对象编程,而python比较灵活即支持面 ...
- [.net 面向对象编程基础] (11) 面向对象三大特性——封装
[.net 面向对象编程基础] (11) 面向对象三大特性——封装 我们的课题是面向对象编程,前面主要介绍了面向对象的基础知识,而从这里开始才是面向对象的核心部分,即 面向对象的三大特性:封装.继承. ...
- 面向对象编程(九)——面向对象三大特性之继承以及重写、Object类的介绍
面向对象三大特性 面向对象三大特征:继承 :封装/隐藏 :多态(为了适应需求的多种变化,使代码变得更加通用!) 封装:主要实现了隐藏细节,对用户提供访问接口,无需关心方法的具体实现. 继承:很好的实现 ...
- C++面向对象三大特性
面向对象三大特性 继承 public protected private public继承 public protected 不可见 private继承 private private 不可见 pro ...
- day20面向对象三大特性 , 类嵌套
#!/usr/bin/env python# -*- coding:utf-8 -*- # 1.简述面向对象三大特性并用代码表示."""封装:class Account: ...
随机推荐
- EF模型+MySql问题
1.添加ADO.NET模型不出现MYSQL连接时: a.确认mysql-for-visualstudio-1.2.7.msi是否安装 b.确认.net版本是否为4.6 2.已经选定了连接,但是在下一步 ...
- [JZOJ4899] 雪之国度
题目描述 雪之国度有N座城市,依次编号为1到N,又有M条道路连接了其中的城市,每一条道路都连接了不同的2个城市,任何两座不同的城市之间可能不止一条道路.雪之女王赋予了每一座城市不同的能量,其中第i座城 ...
- 使用listView有感
et listView = new ccui.ListView();this.addChild(listView,9999);listView.setDirection(ccui.ScrollView ...
- python:文件读写
#!/usr/bin/python# -*- coding:utf-8 -*- #!/usr/bin/python# -*- coding:utf-8 -*- file1 = open('a.txt' ...
- socket编程(python)
交互原理: 服务端和客户端通过底层socket接口编程通信,交互的信息都是通过byte字节形式传递,网络传输中不能保证信息完整传输有可能是分片传输,所以可能从缓冲区获取的信息需要分段拼接或拆分组合成一 ...
- Python获取当前文件路径及父文件路径
import os # 当前文件的路径 1.os.getcwd(): 2.os.path.realpath(__file__) # 当前文件的父路径 1.pwd=os.getcwd() os.pa ...
- linux命令 EOF
在shell脚本中,通常将EOF与 << 结合使用,表示后续的输入作为子命令或子Shell的输入,直到遇到EOF为止,再返回到主Shell. EOF只是一个分界符,当然也可以用abcde替 ...
- django 搭建一个投票类网站(二)
前一篇讲了创建一个工程和一个polls的应用程序,以及配置了数据库. 这篇就继续讲吧 1.django admin模块 admin模块是django自带的模块,他让开发者可以不用管写任何代码的情况下就 ...
- [thinkphp] 启用__PUBLIC__
我真是受够了,,, 为了解决__PUBLIC__不能用的问题 我折腾了好几天了,然后终于被我找到了原因 解决过程 首先必须贴出来帮助我的人 https://my.oschina.net/u/12630 ...
- 牛客网刷题总结—Day1
1.关于哈夫曼树 哈夫曼树也称最优二叉树,其n个叶子节点都是带有权值的,其节点的带权路径长度(n个叶子节点的权值*其到根节点的路径之和)最小的二叉树即为哈夫曼树. 一般的哈夫曼树不存在度为1的节点(除 ...