多态是同一个行为具有多个不同表现形式或形态的能力。多态性意味着有多重形式,在面向对象编程范式中,多态性往往表现为“一个接口,多个功能”。
多态性可以是静态的或动态的:
在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自Object。多态就是同一个接口,使用不同的实例而执行不同操作。
简单来说,就是同一个事件发生在不同的对象上会产生不同的结果。例如现实中我们按下 F1 键这个动作:
- 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
- 如果当前在 Word 下弹出的就是 Word 帮助;
- 在 Windows 下弹出的就是 Windows 帮助和支持。
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定,这种静态绑定就是构成静态多态的关键。换句话说,静态多态是指在编译时就能够确定具体调用哪个函数的多态性。C# 提供了两种技术来实现静态多态性。分别为:
我们可以在同一个范围内对相同的函数名有多个定义,但函数的定义必须彼此不同:
下面的实例演示了几个相同的函数 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
我们可以重定义或重载 C# 中内置的运算符。因此,程序员也可以使用自定义类型的运算符,来指定类对象之间的运算操作。
operator
后跟运算符的符号来定义的。重载运算符的语法格式为:
public static 返回类型 operator 运算符(参数列表)
{
// 实现运算符的操作
return 运算结果;
}
其中,返回类型可以是任何有效的C#类型,参数列表可以包含一个或多个输入参数。
public
和static
关键字修饰的,并且必须定义在类或结构体中,因此只会影响定义或继承了重载运算符的类或结构体的运算。运算符 | 描述 |
---|---|
+(正), -(负), !, ~, ++, – | 这些一元运算符只有一个操作数,且可以被重载。 |
+, -, *, /, % | 这些二元运算符带有两个操作数,且可以被重载。 |
==, !=, <, >, <=, >= | 这些比较运算符可以被重载。 |
&&, || | 这些条件逻辑运算符不能被直接重载。 |
+=, -=, *=, /=, %= | 这些赋值运算符不能被 |
=, ., ?:, ->, 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)
动态多态是指在运行时根据对象类型来确定调用哪个函数的多态性。例如,一个基类可以有一个虚函数,派生类可以重写这个虚函数,然后在运行时根据对象类型调用适当的函数。这种多态性是通过抽象类继承和虚函数实现的
在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类的引用。
补充一下,使用sealed
关键字可以将一个类声明为不能被继承的,或将一个方法声明为不能被重写的。
sealed
关键字只能用于类和方法,不能用于字段、属性或事件。- 抽象类不能被声明为
sealed
!sealed class MyClass { // ... }
在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 声明的。
- 虚方法可以在不同的继承类中有不同的实现。
- 对虚方法的调用是在运行时发生的。