十、面向对象 之 多态

  • 静态多态
    • 函数重载
    • 运算符重载
  • 动态多态
    • 抽象类
    • 虚方法

1、概念

  • 同父类对象执行相同方法的不同表现形态
    • 补充:函数重载也是一种多态(编译时多态)
  • 本章学习内容:运行时多态
    • vob
      • v:virtual(虚函数)
      • o:override(重写)
      • b:base(父类)
    • 抽象类和抽象方法
    • 接口
  • 解决问题:让同一个对象有唯一的行为特征

● 小栗子

  • new 关键字的新用法:子类重写父类需要用 new 关系修饰方法,不然会报警告你省略了关键字
class Person
{
    public string Name;
    public int Age;

    public void Speak()
    {
        Console.Write("我叫{0},今年{1}岁了。", Name, Age);
    }
}

class Teacher : Person
{
    public string Subject; // 教学科目

    // 重写父类方法
    public new void Speak() {
        base.Speak(); // 虽然重写了,但是父类你也别闲着,我依然可以用你的功能
        Console.WriteLine("我是你们的{0}老师,现在开始上课。", Subject);
    }
}

class Student : Person
{
    public string School;

    public new void Speak()
    {
        base.Speak();
        Console.WriteLine("我在{0}上学。", School);
    }
}

// 某方法内↓↓↓
Person pes = new Person();
pes.Name = "Mr.Bai";
pes.Age = 24;
pes.Speak(); // 我叫Mr.Bai,今年24岁了。
Console.WriteLine();

Person tec = new Teacher();
tec.Name = "peiqi";
tec.Age = 24;
tec.Speak(); // 我叫peiqi,今年24岁了。
Teacher t = tec as Teacher;
t.Subject = "C/C++/Java/C#课";
t.Speak(); // 我叫peiqi,今年24岁了。我是你们的C/C++/Java/C#课老师,现在开始上课。

Person stu = new Student();
stu.Name = "qiaozhi";
stu.Age = 10;
stu.Speak(); // 我叫qiaozhi,今年10岁了。
Student s = stu as Student;
s.School = "宇宙佩奇小学";
s.Speak(); // 我叫qiaozhi,今年10岁了。我在宇宙佩奇小学上学。

2、virtual、override、base

  • v:virtual(虚函数)
  • o:override(重写)
  • b:base(父类)

修改上面的小栗子,让同一个对象有唯一的行为特征

class Person
{
    public string Name;
    public int Age;

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    // 虚函数
    public virtual void Speak()
    {
        Console.Write("我叫{0},今年{1}岁了。", Name, Age);
    }
}

class Teacher : Person
{
    public string Subject; // 教学科目

    public Teacher(string name, int age, string subject) : base(name, age) 
    {
        Subject = subject;
    }

    // 重写父类方法
    public override void Speak()
    {
        base.Speak(); // base代表父类
        Console.WriteLine("我是你们的{0}老师,现在开始上课。", Subject);
    }
}

class Student : Person
{
    public string School;

    public Student(string name, int age, string school) : base(name, age)
    {
        School = school;
    }

    // 重写父类方法
    public override void Speak()
    {
        base.Speak();
        Console.WriteLine("我在{0}上学。", School);
    }
}

// 某方法内↓↓↓
Person pes = new Person("Mr.Bai", 24);
pes.Speak(); // 我叫Mr.Bai,今年24岁了。
Console.WriteLine();

Person tec = new Teacher("peiqi", 24, "C/C++/Java/C#课");
tec.Speak(); // 我叫peiqi,今年24岁了。我是你们的C/C++/Java/C#课老师,现在开始上课。

Person stu = new Student("qiaozhi", 10, "宇宙佩奇小学");
stu.Speak(); // 我叫qiaozhi,今年10岁了。我在宇宙佩奇小学上学。

3、抽象类和抽象方法

3.1、抽象类

  • 关键字:abstract
  • 特点
    • 不能被实例化(但是依然适用里氏替换原则)
    • 可以包含抽象方法(有抽象方法的一定是抽象类!)
    • 继承抽象类必须重写其抽象方法
abstract class Thing
{ 
    // 这里可以写类的任何东西 + 抽象方法
}

3.2、抽象方法

  • 关键字:abstract
  • abstract 修饰的方法叫抽象方法,又叫 纯虚方法
  • 特点
    • 只能在抽象类中声明
    • 没有方法体
    • 不能私有
    • 抽象类继承后必须用 override 重写抽象方法
  • 如何选择抽象类和普通类
    • 不希望被实例化的类,选抽象类就完了
abstract class Person
{
    public string Name;
    public int Age;

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    // 抽象方法
    public abstract void Speak();
}

class Teacher : Person
{
    public string Subject; // 教学科目

    public Teacher(string name, int age, string subject) : base(name, age)
    {
        Subject = subject;
    }

    // 重写父类方法
    public override void Speak()
    {
        Console.Write("我叫{0},今年{1}岁了。", Name, Age);
        Console.WriteLine("我是你们的{0}老师,现在开始上课。", Subject);
    }
}

class Student : Person
{
    public string School;

    public Student(string name, int age, string school) : base(name, age)
    {
        School = school;
    }

    public override void Speak()
    {
        Console.Write("我叫{0},今年{1}岁了。", Name, Age);
        Console.WriteLine("我在{0}上学。", School);
    }
}

// 某方法内↓↓↓
Person tec = new Teacher("peiqi", 24, "C/C++/Java/C#课");
tec.Speak(); // 我叫peiqi,今年24岁了。我是你们的C/C++/Java/C#课老师,现在开始上课。

Person stu = new Student("qiaozhi", 10, "宇宙佩奇小学");
stu.Speak(); // 我叫qiaozhi,今年10岁了。我在宇宙佩奇小学上学。

4、接口

4.1、概念

  • 关键字:interface
  • 接口是行为的抽象规范,它是一种自定义类型
    • 个人理解:接口就是定义一套规范、模板,谁继承这个接口,谁就要遵守这个规范套用这个模板
  • 特点
    • 和类声明类似
    • 接口也是用来继承的
    • 接口不能被实例化,但依然适用里氏替换原则

4.2、语法

  • 不能有成员变量
  • 只可有属性、方法、索引器、事件
  • 成员不能被实现
  • 成员可不写访问修饰符(不写默认public),不能是私有的,如果是protected则需要显示实现
  • 接口不能继承类,只能继承另一个接口
  • 接口名一般以 I 开头 + 帕斯卡命名
// 语法
interface 接口名
{
    // 属性、方法、索引器、事件
}

// 栗子
interface IFly
{
    // 属性
    int Height
    {
        get; // get、set也不能包含方法体
        set; // get、set也不可能全省略不写
    }

    // 方法
    void Fly();

    // 索引
    int this[int index]
    {
        get; // get、set使用与属性的类似
        set;
    }

    // 事件
    event Action doSomething;
}

4.3、使用

  • 类可以多实现接口
  • 类继承接口必须实现其所有成员
  • 接口可以继承接口,不需要实现,待普通类继承之后实现所有成员
  • 实现的接口函数可以加 virtual 再在子类重写
  • 接口不能被实例化,但依然适用里氏替换原则
// 接口
interface IFly
{
    // 属性
    int Height 
    {
        get; // get、set也不能包含方法体
        set; // get、set也不可能全省略不写
    }

    // 方法
    void Fly();

    // 索引
    int this[int index]
    {
        get; // get、set使用与属性的类似
        set;
    }

    // 事件
    event Action doSomething;
}

// 接口继承接口小栗子
interface IXxx : IFly // 接口是可以多继承接口的,继续加,就完了
{
    // 这里写IXxx的成员,不需要实现IFly成员,实现只留给普通类
}

// 一个无关紧要的基类
abstract class Person
{
    public string Name;
    public int Age;

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public virtual void Speak()
    {
        Console.Write("我叫{0},今年{1}岁了。", Name, Age);
    }
}

// 接口的实现(掺杂继承。。。)
class Teacher : Person,IFly // 接口多继承后面可以无限,
{
    public string Subject; // 教学科目

    public Teacher(string name, int age, string subject) : base(name, age)
    {
        Subject = subject;
    }

    // 重写父类方法
    public override void Speak()
    {
        base.Speak();
        Console.WriteLine("我是你们的{0}老师,现在开始上课。", Subject);
    }

    // 以下为实现接口的成员
    int IFly.Height 
    { 
        get;
        set;
    }

    public int this[int index] 
    {
        get { return 0; }
        set { }
    }

    public event Action doSomething;

    public void Fly()
    {
        Speak();
        Console.WriteLine("老师可以借助飞机飞行。");
    }
}

// 某方法内↓↓↓
IFly fly = new Teacher("佩奇", 10, "幼儿园老师");
fly.Fly(); // 我叫佩奇,今年10岁了。我是你们的幼儿园老师老师,现在开始上课。\n老师可以借助飞机飞行。

4.4、显式实现接口

4.4.1、需要使用显式实现接口的两种情况

  • ==接口里成员的访问修饰符是 protected ==
  • 继承多个接口,其中有同名方法

注!!!显式实现接口不能写访问修饰符

4.4.2、栗子

  • 两个包含同名方法的接口
// 能吃接口
interface IEat
{
    void Eat();
}

// 超能吃接口
interface ISuperEat
{
    void Eat();
}
  • 普通的实现会导致两个行为变成了同一种表现形式
    • 语法上没问题,但是别忘了接口设计的初衷
class MyEat : IEat, ISuperEat
{
    public void Eat() 
    {
        Console.WriteLine("我能吃,而且超能吃!");
    }
}

// 某方法内↓↓↓
IEat eat = new MyEat();
eat.Eat(); // 我能吃,而且超能吃!
ISuperEat superEat = new MyEat();
superEat.Eat(); // 我能吃,而且超能吃!
  • 显示实现
    • 就是实现的时候用 接口名.行为名
    • 能过实现不同规范行为的不同表现形式
    • 但是!实现类就不能自己点出实现的行为了,只能转成父类才能点对应的行为
      • 当然实现类也可以有自己的 Eat 方法,这时 MyEat 里就有 3 个 Eat 方法了,此时 MyEat 对象 mine 就可以调(也只能调)自己的 mine.Eat(); 方法了
      • 注意!上面说的 3 个同名方法不是重载哦!
class MyEat : IEat, ISuperEat
{
    void IEat.Eat()
    {
        Console.WriteLine("我能吃!");
    }

    void ISuperEat.Eat()
    {
        Console.WriteLine("我超能吃!");
    }
}

// 某方法内↓↓↓
IEat eat = new MyEat();
eat.Eat(); // 我能吃!
ISuperEat superEat = new MyEat();
superEat.Eat(); // 我超能吃!

// 此时实现类就不能自己点出实现的行为了,只能转成父类才能点对应的行为
MyEat mine = new MyEat();
(mine as IEat).Eat(); // 我能吃!
(mine as ISuperEat).Eat(); // 我超能吃!

5、密封方法

  • 关键字:sealed
  • 作用:用 sealed 关键字 修饰的实现类中实现的 虚方法 或 抽象方法,其子类不可再重写此虚方法 或 抽象方法
  • 特点:和 overide 一起出现
// 一个普通的抽象类
abstract class AbsTest
{
    public string name;
    public abstract void Eat();

    public virtual void Say() 
    {
        Console.WriteLine("阿巴阿巴阿巴~");
    }
}

// 一个普通的类
class Test : AbsTest
{
    public sealed override void Eat() // 我用 sealed 修饰吃了,后面的一个也别想吃了!嘿嘿!
    {
        Console.WriteLine("吃就完了!");
    }

    public override void Say()
    {
        // base.Say();
        Console.WriteLine("嘎嘎嘎!");
    }
}

// 一个更普通的类
class TestSon : Test
{
    // 由于 Eat 被 Test 用 sealed 修饰了,我就啥也吃不到了
    // 虚方法我也懒得实现了,毕竟那个也不是强制的
}

6、(+)运算符重载

  • 语法:
    • public static T operator [symbol] (T t1, T t2) { return T; }
  • demo:
class Box
{
    public int length;
    public int width;
    public int height;

    public Box(int length, int width, int height)
    {
        this.length = length;
        this.width = width;
        this.height = height;
    }

    public static Box operator +(Box a, Box b){
        return new Box(a.length + b.length, a.width + b.width, a.height + b.height);
    }

    public override string ToString()
    {
        return new StringBuilder()
        		.Append("{ length=").Append(length)
	            .Append("; width=").Append(width)
	            .Append("; height=").Append(height)
	            .Append(" }").ToString();
    }
}

// 测试代码
Console.WriteLine(new Box(1, 1, 1) + new Box(9, 9, 9)); // { length=10; width=10; height=10 }
  • 可重载运算符和不可重载运算符:
    在这里插入图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯纯的小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值