unity的C#学习——静态多态性(函数重载、运算符重载)与动态多态性(抽象类、虚方法)

文章目录

  • 多态性
    • 1、静态多态性
      • 1.1 函数重载——利用参数差异
      • 1.2 运算符重载——operator
    • 2、动态多态性
      • 2.1 抽象类/成员——abstract
      • 2.2 不可继承类/方法——sealed
      • 2.3 虚方法——virtual


多态性

多态是同一个行为具有多个不同表现形式或形态的能力。多态性意味着有多重形式,在面向对象编程范式中,多态性往往表现为“一个接口,多个功能”

多态性可以是静态的或动态的:

  • 静态多态性中,函数的响应是在编译时发生的,主要借由重载(overload)实现。
  • 动态多态性中,函数的响应是在运行时发生的,主要借由重写(override)实现。

在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自Object。多态就是同一个接口,使用不同的实例而执行不同操作。

简单来说,就是同一个事件发生在不同的对象上会产生不同的结果。例如现实中我们按下 F1 键这个动作:

  • 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
  • 如果当前在 Word 下弹出的就是 Word 帮助;
  • 在 Windows 下弹出的就是 Windows 帮助和支持。

1、静态多态性

在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定,这种静态绑定就是构成静态多态的关键。换句话说,静态多态是指在编译时就能够确定具体调用哪个函数的多态性。C# 提供了两种技术来实现静态多态性。分别为:

  • 函数重载
  • 运算符重载

1.1 函数重载——利用参数差异

我们可以在同一个范围内对相同的函数名有多个定义,但函数的定义必须彼此不同

  • 可以是参数列表中的参数类型不同
  • 可以是参数个数不同
  • 只有返回类型不同的函数声明不能重载。

下面的实例演示了几个相同的函数 Add(),用于对不同个数参数进行相加处理:

using System;
namespace PolymorphismApplication
{
    public class TestData  
    {  
    	// 通过参数的个数差异实现重载
        public int Add(int a, int b, int c)  
        {  
            return a + b + c;  
        }  
        public int Add(int a, int b)  
        {  
            return a + b;  
        }  
    }  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            TestData dataClass = new TestData();
            int add1 = dataClass.Add(1, 2);  
            int add2 = dataClass.Add(1, 2, 3);

            Console.WriteLine("add1 :" + add1);
            Console.WriteLine("add2 :" + add2);  
        }  
    }  
}

下面的实例演示了几个相同的函数 print(),用于打印不同的数据类型:

using System;
namespace PolymorphismApplication
{
   class Printdata
   {
   	  // 通过参数的类型差异实现重载
      void print(int i)
      {
         Console.WriteLine("输出整型: {0}", i );
      }

      void print(double f)
      {
         Console.WriteLine("输出浮点型: {0}" , f);
      }

      void print(string s)
      {
         Console.WriteLine("输出字符串: {0}", s);
      }
      static void Main(string[] args)
      {
         Printdata p = new Printdata();
         // 调用 print 来打印整数
         p.print(1);
         // 调用 print 来打印浮点数
         p.print(1.23);
         // 调用 print 来打印字符串
         p.print("Hello Runoob");
         Console.ReadKey();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

输出整型: 1
输出浮点型: 1.23
输出字符串: Hello Runoob

1.2 运算符重载——operator

我们可以重定义或重载 C# 中内置的运算符。因此,程序员也可以使用自定义类型的运算符,来指定类对象之间的运算操作。

  • 重载运算符是具有特殊名称的函数,是通过关键字 operator 后跟运算符的符号来定义的。
  • 与其他函数一样,重载运算符有返回类型和参数列表

重载运算符的语法格式为:

public static 返回类型 operator 运算符(参数列表)
{
    // 实现运算符的操作
    return 运算结果;
}

其中,返回类型可以是任何有效的C#类型,参数列表可以包含一个或多个输入参数

  • 重载运算符必须是publicstatic关键字修饰的,并且必须定义在类或结构体中,因此只会影响定义或继承了重载运算符的类或结构体的运算。
运算符 描述
+(正), -(负), !, ~, ++, – 这些一元运算符只有一个操作数,且可以被重载
+, -, *, /, % 这些二元运算符带有两个操作数,且可以被重载
==, !=, <, >, <=, >= 这些比较运算符可以被重载
&&, || 这些条件逻辑运算符不能被直接重载
+=, -=, *=, /=, %= 这些赋值运算符不能被重载
=, ., ?:, ->, new, is, sizeof, typeof 这些运算符不能被重载

下面是一个简单的例子,演示了如何重载加法运算符:

class Vector
{
    public int X { get; set; }
    public int Y { get; set; }

    public Vector(int x, int y)
    {
        X = x;
        Y = y;
    }

    public static Vector operator +(Vector a, Vector b) //重载加法运算符,仅影响Vector对象的加法运算
    {
        return new Vector(a.X + b.X, a.Y + b.Y); //在重载运算符函数内部并不是对Vector对象的运算,而是对于其字段的运算,所以不受影响
    }
}

class Program
{
    static void Main(string[] args)
    {
        Vector a = new Vector(1, 2);
        Vector b = new Vector(3, 4);
        Vector c = a + b;
        Console.WriteLine("c = ({0}, {1})", c.X, c.Y);
    }
}

在这个例子中,定义了一个Vector类,其中重载了加法运算符。当使用加法运算符对两个Vector对象进行加法运算时,将调用该类中定义的加法运算符函数,完成运算操作。

子类可以继承父类中定义的运算符重载,但是子类也可以选择覆盖这些运算符重载,以便自定义子类特定的行为。注意这里的覆写是函数重载,和后面的虚函数不一样:

using System;

// 定义基类 A
class A
{
    // 定义公有成员变量 X
    public int X;

    // 定义构造函数,初始化 X
    public A(int x)
    {
        X = x;
    }

    // 定义运算符重载,实现 A 之间的加法操作
    public static A operator +(A a, A b)
    {
        return new A(a.X + b.X);
    }
}

// 定义派生类 B,继承自 A
class B : A
{
    // 定义公有成员变量 Y
    public int Y;

    // 定义构造函数,初始化 X 和 Y
    public B(int x, int y) : base(x)
    {
        Y = y;
    }

    // 子类覆写运算符重载,实现 B 之间的加法操作
    public static B operator +(B a, B b)
    {
        return new B(a.X + b.X, a.Y + b.Y);
    }
}

// 定义入口类 Program
class Program
{
    static void Main(string[] args)
    {
        // 实例化 A 对象 a1 和 a2,以及其加法运算的结果 a3
        A a1 = new A(1);
        A a2 = new A(2);
        A a3 = a1 + a2;
        Console.WriteLine("a1 + a2 = " + a3.X);

        // 实例化 B 对象 b1 和 b2,以及其加法运算的结果 b3
        B b1 = new B(1, 2);
        B b2 = new B(3, 4);
        B b3 = b1 + b2;
        Console.WriteLine("b1 + b2 = (" + b3.X + ", " + b3.Y + ")");
    }
}

这段代码的输出结果是:

a1 + a2 = 3
b1 + b2 = (4, 6)


2、动态多态性

动态多态是指在运行时根据对象类型来确定调用哪个函数的多态性。例如,一个基类可以有一个虚函数,派生类可以重写这个虚函数,然后在运行时根据对象类型调用适当的函数。这种多态性是通过抽象类继承虚函数实现的

2.1 抽象类/成员——abstract

在C#中,抽象类是一种特殊的类,它不能被实例化,而只能作为其他类的基类来使用。抽象类包含抽象成员,这些成员没有实现,需要由子类来实现。抽象类用于定义一组相关的类的通用行为和属性,而不是为特定的类实现具体的行为和属性。

  • 定义抽象类时,需要在类名前面添加abstract关键字。
  • 抽象类中可以包含普通的方法、属性、字段等成员,也可以包含抽象成员,即没有实现的方法或属性。
  • 抽象成员需要在声明时使用abstract关键字,并且不能包含方法体(即方法的主体执行代码)。
  • 抽象类也可以继承自其他类或抽象类。
  • 不能创建一个抽象类的实例 ,不能在一个抽象类外部声明一个抽象方法。
  • 子类继承抽象类时,必须实现抽象类中的所有抽象成员。如果子类没有实现抽象成员,则子类也必须声明为抽象类

以下是一个使用抽象类的示例:

// 定义抽象类 Shape
abstract class Shape
{
    public abstract double GetArea();
}

// 定义矩形类 Rectangle,继承自 Shape
class Rectangle : Shape
{
    double length;
    double width;

    // 定义构造函数,传入矩形的长度和宽度
    public Rectangle(double l, double w)
    {
        length = l;
        width = w;
    }

    // 实现抽象类 Shape 的抽象方法 GetArea(),计算矩形面积
    public override double GetArea()
    {
        return length * width;
    }
}

// 定义三角形类 Triangle,继承自 Shape
class Triangle : Shape
{
    double bottom;
    double height;

    // 定义构造函数,传入三角形的底和高
    public Triangle(double b, double h)
    {
        bottom = b;
        height = h;
    }

    // 实现抽象类 Shape 的抽象方法 GetArea(),计算三角形面积
    public override double GetArea()
    {
        return 0.5 * bottom * height;
    }
}

// 程序入口类 Program
class Program
{
    static void Main(string[] args)
    {
        // 创建 Rectangle 实例 rect,传入矩形的长度和宽度
        Shape rect = new Rectangle(5, 10);
        // 打印矩形面积
        Console.WriteLine("矩形的面积为:" + rect.GetArea());

        // 创建 Triangle 实例 tri,传入三角形的底和高
        Shape tri = new Triangle(5, 10);
        // 打印三角形面积
        Console.WriteLine("三角形的面积为:" + tri.GetArea());
    }
}

在上面的示例中,我们定义了一个抽象类Shape,其中包含一个抽象方法GetArea,用于计算图形的面积。

然后定义了两个子类Rectangle和Triangle,分别实现了GetArea方法。在Main方法中,分别使用这两个子类来计算图形的面积。由于Shape是抽象类,不能被实例化,因此使用子类来实例化Shape类的引用。

2.2 不可继承类/方法——sealed

补充一下,使用sealed关键字可以将一个类声明为不能被继承的,或将一个方法声明为不能被重写的。

  • sealed关键字只能用于类和方法,不能用于字段、属性或事件
  • 抽象类不能被声明为sealed
sealed class MyClass
{
    // ...
}

2.3 虚方法——virtual

在C#中,虚方法指的是在基类中定义的方法,可以被子类重写。在基类中使用virtual关键字来定义虚方法,而在子类中可以使用override关键字来重写虚方法。

  • 抽象类是提炼出大家共用的方法但自己不实现也不能用,留给子类实现并使用。
  • 虚方法则不同,在基类中已经实现并可以使用该方法,同时还允许子类根据自己的需要重新实现(改写)基类中的方法

例如,基类中可能有一个 Draw 方法,用于绘制某个图形,但是不同的子类可能需要绘制不同的图形,因此可以重写 Draw 方法,以便子类可以实现自己独特的绘制功能。下面是一个使用虚方法的示例代码:

using System;

class Shape
{
    public virtual void Draw() //定义虚方法
    {
        Console.WriteLine("绘制一个图形");
    }
}

class Rectangle : Shape
{
    public override void Draw() //重写虚方法
    {
        Console.WriteLine("绘制一个矩形");
    }
}

class Circle : Shape
{
    public override void Draw() //重写虚方法
    {
        Console.WriteLine("绘制一个圆形");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Shape shape = new Shape();
        shape.Draw();

        Shape rect = new Rectangle();
        rect.Draw();

        Shape circle = new Circle();
        circle.Draw();
    }
}

在上面的代码中,Shape 类中定义了一个虚方法 Draw,子类 Rectangle 和 Circle 分别重写了这个方法,以便实现自己独特的绘制功能。在 Program 类中,我们创建了一个 Shape 对象、一个 Rectangle 对象和一个 Circle 对象,并且都调用了它们的 Draw 方法。由于 Rectangle 和 Circle 类都重写了 Draw 方法,因此它们的 Draw 方法输出的结果分别是“绘制一个矩形”和“绘制一个圆形”。

由此可以知道:

  • 虚方法是使用关键字 virtual 声明的。
  • 虚方法可以在不同的继承类中有不同的实现。
  • 对虚方法的调用是在运行时发生的。

你可能感兴趣的:(unity的c#之旅,unity,c#,学习,开发语言)