• C# 面向对象之多态


    目录

    一、简介

    二、函数的重载

    三、运算符的重载

    可重载运算符

    不可重载运算符

    运算符重载案例

    四、动态多态性

    1.虚方法

    2.抽象类和抽象方法

    五、interface

    结束


    一、简介

    多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。

    多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。

    静态多态性

    在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:

    • 函数重载
    • 运算符重载

    动态多态性

    是通过 抽象类 和 虚方法 实现的。


    使用多态的好处
    1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。


    2. 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。

    二、函数的重载

    在同一个类中。允许同名方法。他们的参数个数,或参数类型不同即可。

    函数的重载在平常用的还是比较多的,下面代码中两个 Add 方法,就属于函数的重载

    1. public class Compute
    2. {
    3. public int Add(int x,int y)
    4. {
    5. return x + y;
    6. }
    7. public float Add(float x,float y)
    8. {
    9. return x + y;
    10. }
    11. }

    三、运算符的重载

    用户定义的类型可重载预定义的 C# 运算符。 也就是说,当一个或两个操作数都是某类型时,此类型可提供操作的自定义实现。 可重载运算符部分介绍了哪些 C# 运算符可重载。

    使用 operator 关键字来声明运算符。 运算符声明必须符合以下规则:

    • 同时包含 public 和 static 修饰符。
    • 一元运算符有一个输入参数。 二元运算符有两个输入参数。 在每种情况下,都至少有一个参数必须具有类型 T 或 T?,其中 T 是包含运算符声明的类型。

    参考:

    运算符重载 - C# 引用 | Microsoft Learn

    重载运算符

    运算符限制
    +x -x !x ~x ++ -- true false无。
    x + y x - y x * y x / y x % y x & y
    x | y x ^ y x << y x >> y
    无。
    x == y x != y x < y x > y x <= y x >= y必须成对重载:== and !=< and ><= and >=

    不可重载运算符

    运算符备选方法
    x && y x || y重载 true 和 false 运算符以及 & 或 | 运算符。 有关详细信息,请参阅用户定义的条件逻辑运算符
    a[i] a?[i]定义索引器
    (T)x定义可由强制转换表达式执行的自定义类型转换。 有关详细信息,请参阅用户定义转换运算符
    += -= \*= /= %= &= |= ^= <<= >>=重载相应的二元运算符。 例如,重载 + 也会隐式重载 +=
    ^x x = y x.y x?.y c ? t : f x ?? y ??= y
    x..y x->y => f(x) as await checked unchecked default delegate is nameof new
    sizeof stackalloc switch typeof with
    无。

    运算符重载案例

    看到这里,估计很多人会问,这是什么一堆乱七八糟的东西,说了半天,运算符的重载到底是什么?

    下面代码,你可能会用过

    1. Version v1 = new Version(txt1.Text);
    2. Version v2 = new Version(txt2.Text);
    3. if (v1 > v2)
    4. {
    5. MessageBox.Show("版本1高于版本2");
    6. }
    7. if (v1 < v2)
    8. {
    9. MessageBox.Show("版本1低于版本2");
    10. }

    那么,运算符的重载在哪?看到 if(v1 > v2) 这句没有,这里的 “>” 就是运算符的重载,v1 和 v2 明明是两个类,这个是怎么用运算符进行运算的?那么下面就介绍运算符的重载到底是怎么运用的。

    1. using System;
    2. namespace Test1
    3. {
    4. internal class Program
    5. {
    6. static void Main(string[] args)
    7. {
    8. MyPoint myPoint1 = new MyPoint();
    9. myPoint1.X = 2;
    10. myPoint1.Y = 3;
    11. MyPoint myPoint2 = new MyPoint();
    12. myPoint2.X = 4;
    13. myPoint2.Y = 5;
    14. MyPoint myPoint3 = myPoint1 + myPoint2;
    15. Console.WriteLine("X:" + myPoint3.X);
    16. Console.WriteLine("Y:" + myPoint3.Y);
    17. Console.ReadKey();
    18. }
    19. }
    20. public class MyPoint
    21. {
    22. public int X { get; set; }
    23. public int Y { get; set; }
    24. public static MyPoint operator +(MyPoint p1, MyPoint p2)
    25. {
    26. MyPoint myPoint = new MyPoint();
    27. myPoint.X = p1.X + p2.X;
    28. myPoint.Y = p1.Y + p2.Y;
    29. return myPoint;
    30. }
    31. }
    32. }

    运行:

    X:6
    Y:8

    这里的 X,Y 的结果,刚好是 myPoint1 和 myPoint2 相加的结果。

    四、动态多态性

    1.虚方法

    当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法,虚方法是使用关键字 virtual 声明的,虚方法可以在不同的继承类中有不同的实现,即为基类中定义的允许在派生类中重写的方法。

    注意:
    1.因为虚方法需要被子类调用,所以访问修饰符不能为private
    2.父类虚方法使用的什么访问修饰符,子类重写就必须用什么访问修饰符
     

    案例

    1. using System;
    2. namespace Test1
    3. {
    4. internal class Program
    5. {
    6. static void Main(string[] args)
    7. {
    8. Bird bird = new Sparrow();
    9. bird.Eat();
    10. bird = new Eagle();
    11. bird.Eat();
    12. Console.ReadKey();
    13. }
    14. }
    15. public class Bird
    16. {
    17. //吃
    18. public virtual void Eat()
    19. {
    20. Console.WriteLine("Bird-Eat");
    21. }
    22. }
    23. //麻雀
    24. public class Sparrow : Bird
    25. {
    26. public override void Eat()
    27. {
    28. Console.WriteLine("麻雀吃稻谷");
    29. }
    30. }
    31. //老鹰
    32. public class Eagle : Bird
    33. {
    34. public override void Eat()
    35. {
    36. Console.WriteLine("老鹰吃坤坤");
    37. }
    38. }
    39. }

    运行:

    麻雀吃稻谷
    老鹰吃坤坤

    2.抽象类和抽象方法

    abstract 修饰符指示被修改内容的实现已丢失或不完整。 abstract 修饰符可用于类、方法、属性、索引和事件。 在类声明中使用 abstract 修饰符来指示某个类仅用作其他类的基类,而不用于自行进行实例化。 标记为抽象的成员必须由派生自抽象类的非抽象类来实现。

    抽象类具有以下功能

    • 抽象类不能实例化。

    • 抽象类可能包含抽象方法和访问器。

    • 无法使用 sealed 修饰符来修改抽象类,因为两个修饰符的含义相反。 sealed 修饰符阻止类被继承,而 abstract 修饰符要求类被继承。

    • 派生自抽象类的非抽象类,必须包含全部已继承的抽象方法和访问器的实际实现。

    在方法或属性声明中使用 abstract 修饰符,以指示该方法或属性不包含实现。

    抽象方法具有以下功能

    • 抽象方法是隐式的虚拟方法。

    • 只有抽象类中才允许抽象方法声明。

    • 由于抽象方法声明不提供实际的实现,因此没有方法主体;方法声明仅以分号结尾,且签名后没有大括号 ({ })。 例如:

      public abstract void MyMethod();  
      

      实现由方法 override 提供,它是非抽象类的成员。

    • 在抽象方法声明中使用 static 或 virtual 修饰符是错误的。

    除了声明和调用语法方面不同外,抽象属性的行为与抽象方法相似。

    • 在静态属性上使用 abstract 修饰符是错误的。

    • 通过包含使用 override 修饰符的属性声明,可在派生类中重写抽象继承属性。

    抽象类和抽象方法在设计模式和框架中用的特别多。

    案例

    1. using System;
    2. namespace Test1
    3. {
    4. internal class Program
    5. {
    6. static void Main(string[] args)
    7. {
    8. Bird bird = new Sparrow();
    9. bird.Eat();
    10. bird = new Eagle();
    11. bird.Eat();
    12. Console.ReadKey();
    13. }
    14. }
    15. public abstract class Bird
    16. {
    17. //吃
    18. public abstract void Eat();
    19. //在抽象类中使用普通的方法和字段一样没问题
    20. public void Birdsong()
    21. {
    22. Console.WriteLine("鸟叫");
    23. }
    24. public int Age { get; set; }
    25. }
    26. //麻雀
    27. public class Sparrow : Bird
    28. {
    29. public override void Eat()
    30. {
    31. Console.WriteLine("麻雀吃稻谷");
    32. }
    33. }
    34. //老鹰
    35. public class Eagle : Bird
    36. {
    37. public override void Eat()
    38. {
    39. Console.WriteLine("老鹰吃坤坤");
    40. }
    41. }
    42. }

    运行:

    麻雀吃稻谷
    老鹰吃坤坤

    五、interface

    接口的使用方法,也可以和多态一样的使用,这就是我为什么在多态的文章中加入了接口,下面是微软官方的一些介绍。

    接口可以是命名空间或类的成员。 接口声明可以包含以下成员的声明(没有任何实现的签名):

    默认接口成员

    上述成员声明通常不包含主体。 从 C# 8.0 开始,接口成员可以声明主体。 接口中的成员主体是默认实现。 具有主体的成员允许接口为不提供重写实现的类和结构提供“默认”实现。 此外,从 C# 8.0 开始,接口可以包括:

    接口的相关内容比较多,可以参考微软的官方文档

    接口 - C# 参考 | Microsoft Learn

    在使用上,和上面的虚方法,抽象方法 用起来也差不多,区别是,接口可以继承多个,但子类不能继承多个父类

    案例

    1. using System;
    2. namespace Test1
    3. {
    4. internal class Program
    5. {
    6. static void Main(string[] args)
    7. {
    8. Bird bird = new Sparrow();
    9. bird.Eat();
    10. bird = new Eagle();
    11. bird.Eat();
    12. Console.ReadKey();
    13. }
    14. }
    15. public interface Bird
    16. {
    17. //吃
    18. void Eat();
    19. }
    20. //麻雀
    21. public class Sparrow : Bird
    22. {
    23. public void Eat()
    24. {
    25. Console.WriteLine("麻雀吃稻谷");
    26. }
    27. }
    28. //老鹰
    29. public class Eagle : Bird
    30. {
    31. public void Eat()
    32. {
    33. Console.WriteLine("老鹰吃坤坤");
    34. }
    35. }
    36. }

    运行:

    麻雀吃稻谷
    老鹰吃坤坤

    结束

    如果这个帖子对你有用,欢迎 关注 + 点赞 + 留言,谢谢

    end

  • 相关阅读:
    解决uniapp组件uni-file-picker设置:disable-preview=“true“关闭预览不生效的问题 - 禁用图片预览无效的手动解决办法
    Spark 【Spark SQL(一)DataFrame的创建、保存与基本操作】
    初识Python
    Servlet小结
    pdf文件如何防篡改内容
    【Verilog】Verilog基础知识整理
    报告解读:云原生落地传统行业的必然性
    C++中的继承
    【Http】大文件传输 | 与tcp的关系
    ROS功能包编译报错fatal error: xxxxConfig.h: 没有那个文件或目录的解决方法及原理介绍
  • 原文地址:https://blog.csdn.net/qq_38693757/article/details/127124952