重写、多态
上一讲是,子类对父类横向上的扩展
这一讲是,子类对父类纵向上的扩展
方法重写
使用
override
关键字重写父类的方法将父类原本方法的逻辑更新成新版本的逻辑
注:仅能重写可见的父类成员,并且重写要保持签名一致。
签名一致:对函数来说即,返回类型,名称,参数列表
python
中对象有类型,但是变量是没有类型的,变量的类型永远是跟着对象走的。在Python
中没有代差,重写就看不到多态的效果。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OverrideExample {
internal class Program {
static void Main(string[] args) {
/* 重写表示:
* 在继承链上不同的对象实例调用同一个方法,
* 就会触发相对应的版本的方法
*/
var car = new Car();
car.Run();//触发的是Car版本的Run方法
//Car is running.
var vehicle = new Vehicle();
vehicle.Run();//触发的是基类版本的Run方法
//I'm running.
}
}
class Vehicle {
/* virtual关键字
* 表示该方法可被子类重写
*/
public virtual void Run() {
Console.WriteLine("I'm running.");
}
}
class Car : Vehicle {
/* override关键字
* 表示重写父类的方法
* 此时Car这个类中,只有一个Run方法,就是这个类体中的那个重写了的方法
*/
public override void Run() {
Console.WriteLine("Car is running.");
}
}
}
注:当一个类重写了父类的方法后,再被其他类所继承,那么其他类实际上继承的是Car版本的重写后的方法。
using System;
namespace OverrideExample {
internal class Program {
static void Main(string[] args) {
var raseCar = new RaseCar();
raseCar.Run();
//Car is Running.
Console.ReadLine();
}
}
class Vehicle {
public virtual void Run() {
Console.WriteLine("I'm running.");
}
}
class Car : Vehicle {
public override void Run() {
Console.WriteLine("Car is running.");
}
}
class RaseCar : Car {
/* 此时有子类继承Car
* 那么Rase类继承的是Car版本的Run方法
*/
}
}
方法隐藏
如果签名相同的方法在基类和派生类中都进行了声明,但是该方法没有分别声明为
virtual
和override
,派生类就会隐藏基类方法(要使用new
关键字进行声明)。隐藏的话,也就是看不到了,实际这个方法还存在。也就是说,派生类中看不到和基类同名的那个方法了。
using System;
namespace OverrideExample {
internal class Program {
static void Main(string[] args) {
// 父类引用指向子类对象
Animal a = new Dog();
// 调用非虚方法,根据引用的静态类型,调用父类的方法
a.Eat(); // 输出 Animal eats
// 调用虚方法,根据对象的动态类型,调用子类的方法
a.Sleep(); // 输出 Dog sleeps
Console.ReadKey();
}
}
// 基类
public class Animal {
// 非虚方法
public void Eat() {
Console.WriteLine("Animal eats");
}
// 虚方法
public virtual void Sleep() {
Console.WriteLine("Animal sleeps");
}
}
// 子类
public class Dog : Animal {
/* 虽然继承了父类的同名方法,但是因为子类中也有同名的方法,
* 所以子类会隐藏父类的同名的方法,也就是说子类见不到父类的方法
* 表现在,子类类型引用的对象访问不到父类的同名方法。
*
* 隐藏父类的非虚方法
*/
//public new void Eat()
//这两者是等同的
public void Eat() {
Console.WriteLine("Dog eats");
}
// 重写父类的虚方法
public override void Sleep() {
Console.WriteLine("Dog sleeps");
}
}
}
总结一下:
如果使用派生类声明的对象,调用隐藏方法会调用派生类的,如果使用基类声明对象,那么就会调用基类的隐藏方法。
三、虚方法与隐藏方法的区别
1、重写和隐藏的定义
重写:继承时发生,在派生类中重新定义基类中的方法,派生类中的方法和基类的方法是一样的 。例如:基类方法声明为virtual(虚方法),派生类中使用override声明此方法的重写。
隐藏:基类方法不做声明(默认为非虚方法),在派生类中使用new声明此方法的隐藏(new可写可不写)。
2、重写和隐藏的区别
重写(virtaul
)时,定义的变量为基类或派生类, 赋值为派生类时,皆调用基类的重写方法(会从派生类中查找有重写则调用 ,没则调用基类方法)。
隐藏(new
)时,定义的变量为基类,则调用基类的方法(不管赋值是派生类还是基类),定义的变量为派生类则调用派生类的方法。
————————————————
原文链接:https://blog.csdn.net/qq_44034384/article/details/106652112
多态
主要表现在方法和属性上面。
父类的同一种行为,在不同的子类上有不同的实现,这种实现叫做多态。
C#中的多态是指同一个接口或方法,使用不同的对象或参数,可以实现不同的功能或行为。多态可以提高代码的灵活性和可扩展性,实现对象之间的解耦。
C#中的多态可以分为两种:
- 静态多态:也叫编译时多态,是指在编译时就确定了函数的调用。静态多态主要通过函数重载和运算符重载来实现。函数重载是指在同一个类中,可以定义多个同名但参数不同的函数,根据传入的参数类型和个数来决定调用哪个函数。运算符重载是指可以为用户自定义的类型重新定义运算符的含义和行为,使得运算符可以用于操作这些类型的对象。
- 动态多态:也叫运行时多态,是指在运行时才确定了函数的调用。动态多态主要通过虚方法,抽象类和接口来实现。虚方法是指在父类中用
virtual
关键字声明的方法,可以在子类中用override
关键字重写,实现不同的功能。抽象类是指用abstract
关键字声明的类,不能被实例化,只能作为基类,包含抽象方法和非抽象方法。抽象方法是指在抽象类中用abstract
关键字声明的没有方法体的方法,必须在子类中重写。接口是指用interface
关键字声明的一种特殊的抽象类,只包含抽象成员,不能包含字段和构造函数,可以被类实现,实现类必须实现接口中的所有成员。
下面的例子主要是,通过虚方法来展现多态性。最下面有个综合示例。
using System;
namespace OverrideExample {
internal class Program {
static void Main(string[] args) {
/* 多态:
* 使用一个父类的变量,引用一个子类的实例
* 使用这个变量调用被重写的成员的时候,
* 总是能调用到 与所引用的对象实例相对应的 版本
*/
Vehicle veh = new Car();
veh.Run();//调用的Car类的版本
Vehicle veh2 = new RaseCar();
veh2.Run();//调用到RaseCar类的版本
Console.ReadLine();
}
}
class Vehicle {
public virtual void Run() {
Console.WriteLine("I'm running.");
}
}
class Car : Vehicle {
public override void Run() {
Console.WriteLine("Car is running.");
}
}
class RaseCar : Car {
public override void Run() {
Console.WriteLine("Rase Car is running.");
}
}
}
using System;
namespace OverrideExample {
internal class Program {
static void Main(string[] args) {
/* 多态:
* 使用一个父类的变量,引用一个子类的实例
* 使用这个变量调用被重写的成员的时候,
* 总是能调用到 与所引用的对象实例相对应的 版本
*/
Vehicle veh = new Car();
veh.Run();//调用的Car类的版本
Vehicle veh2 = new RaseCar();
veh2.Run();//调用到RaseCar类的版本
Console.ReadLine();
}
}
class Vehicle {
public virtual void Run() {
Console.WriteLine("I'm running.");
}
}
class Car : Vehicle {
public override void Run() {
Console.WriteLine("Car is running.");
}
}
class RaseCar : Car {
public override void Run() {
Console.WriteLine("Rase Car is running.");
}
}
}
综合例子:
using System;
namespace OverrideExample {
internal class Program {
static void Main(string[] args) {
// 创建一个Shape数组,存放不同的Shape对象
Shape[] shapes = new Shape[2];
shapes[0] = new Circle(5, "red");
shapes[1] = new Rectangle(10, 8, "blue");
// 遍历数组,调用每个对象的Area和Draw方法
foreach (Shape shape in shapes) {
Console.WriteLine($"The area is {shape.Area()}");
shape.Draw();
}
// 创建一个IColor数组,存放不同的IColor对象
IColor[] colors = new IColor[2];
colors[0] = new Circle(5, "red");
colors[1] = new Rectangle(10, 8, "blue");
// 遍历数组,调用每个对象的GetColor方法
foreach (IColor color in colors) {
Console.WriteLine($"The color is {color.GetColor()}");
}
}
}
// 定义一个抽象类Shape,包含一个抽象方法Area和一个虚方法Draw
public abstract class Shape {
public abstract double Area();
public virtual void Draw() {
Console.WriteLine("Drawing a shape.");
}
}
// 定义一个接口IColor,包含一个抽象方法GetColor
public interface IColor {
string GetColor();
}
// 定义一个Circle类,继承自Shape类,实现IColor接口,重写Area和Draw方法,实现GetColor方法
public class Circle : Shape, IColor {
private double _radius;
private string _color;
public Circle(double radius, string color) {
_radius = radius;
_color = color;
}
public override double Area() {
return Math.PI * _radius * _radius;
}
public override void Draw() {
Console.WriteLine("Drawing a circle.");
}
public string GetColor() {
return _color;
}
}
// 定义一个Rectangle类,继承自Shape类,实现IColor接口,重写Area和Draw方法,实现GetColor方法
public class Rectangle : Shape, IColor {
private double _length;
private double _width;
private string _color;
public Rectangle(double length, double width, string color) {
_length = length;
_width = width;
_color = color;
}
public override double Area() {
return _length * _width;
}
public override void Draw() {
Console.WriteLine("Drawing a rectangle.");
}
public string GetColor() {
return _color;
}
}
}
重写属性
using System;
namespace OverrideExample {
internal class Program {
static void Main(string[] args) {
Vehicle veh = new Vehicle();
veh.Run();
Console.WriteLine(veh.Speed);
Vehicle car = new Car();
car.Run();
Console.WriteLine(car.Speed);
Console.ReadLine();
}
}
class Vehicle {
private int _speed;
public virtual int Speed {
get => _speed;
set => _speed = value;
}
public virtual void Run() {
Console.WriteLine("I'm running.");
_speed = 100;
}
}
class Car : Vehicle {
private int _rpm;
public override void Run() {
Console.WriteLine("Car is running.");
_rpm = 5000;
}
public override int Speed {
get => _rpm / 100;
set => _rpm = value * 100;
}
}
class RaseCar : Car {
public override void Run() {
Console.WriteLine("Rase Car is running.");
}
}
}
总结
方法隐藏是指当子类声明了一个与父类签名相同的非虚方法时,会隐藏父类中的同名非虚方法。这种情况下,调用该方法时,会根据引用的静态类型来决定调用哪个类的方法,而不是根据对象的动态类型。
这与方法重写不同,方法重写是指当子类重写了父类的虚方法时,会覆盖父类中的同名虚方法。这种情况下,调用该方法时,会根据对象的动态类型来决定调用哪个类的方法,而不是根据引用的静态类型。
无论是重写基类的方法还是重写基类的属性,这两者都必须是
virtual
、abstract
或override
。不能重写非虚的、静态的方法或属性
// 基类
public class Animal
{
// 非虚方法
public void Eat()
{
Console.WriteLine("Animal eats");
}
// 虚方法
public virtual void Sleep()
{
Console.WriteLine("Animal sleeps");
}
}
// 子类
public class Dog : Animal
{
// 隐藏父类的非虚方法
public new void Eat()
{
Console.WriteLine("Dog eats");
}
// 重写父类的虚方法
public override void Sleep()
{
Console.WriteLine("Dog sleeps");
}
}
// 测试
public class Test
{
public static void Main(string[] args)
{
// 父类引用指向子类对象
Animal a = new Dog();
// 调用非虚方法,根据引用的静态类型,调用父类的方法
a.Eat(); // 输出 Animal eats
// 调用虚方法,根据对象的动态类型,调用子类的方法
a.Sleep(); // 输出 Dog sleeps
}
}