C# 面向对象之多态
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。中,函数的响应是在编译时发生的。中,函数的响应是在运行时发生的。多态性可以是静态的或动态的。
目录
一、简介
多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。
静态多态性
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:
- 函数重载
- 运算符重载
动态多态性
是通过 抽象类 和 虚方法 实现的。
使用多态的好处
1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。
2. 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。
二、函数的重载
在同一个类中。允许同名方法。他们的参数个数,或参数类型不同即可。
函数的重载在平常用的还是比较多的,下面代码中两个 Add 方法,就属于函数的重载
public class Compute
{
public int Add(int x,int y)
{
return x + y;
}
public float Add(float x,float y)
{
return x + y;
}
}
三、运算符的重载
用户定义的类型可重载预定义的 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 | 无。 |
运算符重载案例
看到这里,估计很多人会问,这是什么一堆乱七八糟的东西,说了半天,运算符的重载到底是什么?
下面代码,你可能会用过
Version v1 = new Version(txt1.Text);
Version v2 = new Version(txt2.Text);
if (v1 > v2)
{
MessageBox.Show("版本1高于版本2");
}
if (v1 < v2)
{
MessageBox.Show("版本1低于版本2");
}
那么,运算符的重载在哪?看到 if(v1 > v2) 这句没有,这里的 “>” 就是运算符的重载,v1 和 v2 明明是两个类,这个是怎么用运算符进行运算的?那么下面就介绍运算符的重载到底是怎么运用的。
using System;
namespace Test1
{
internal class Program
{
static void Main(string[] args)
{
MyPoint myPoint1 = new MyPoint();
myPoint1.X = 2;
myPoint1.Y = 3;
MyPoint myPoint2 = new MyPoint();
myPoint2.X = 4;
myPoint2.Y = 5;
MyPoint myPoint3 = myPoint1 + myPoint2;
Console.WriteLine("X:" + myPoint3.X);
Console.WriteLine("Y:" + myPoint3.Y);
Console.ReadKey();
}
}
public class MyPoint
{
public int X { get; set; }
public int Y { get; set; }
public static MyPoint operator +(MyPoint p1, MyPoint p2)
{
MyPoint myPoint = new MyPoint();
myPoint.X = p1.X + p2.X;
myPoint.Y = p1.Y + p2.Y;
return myPoint;
}
}
}
运行:
X:6
Y:8
这里的 X,Y 的结果,刚好是 myPoint1 和 myPoint2 相加的结果。
四、动态多态性
1.虚方法
当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法,虚方法是使用关键字 virtual
声明的,虚方法可以在不同的继承类中有不同的实现,即为基类中定义的允许在派生类中重写的方法。
注意:
1.因为虚方法需要被子类调用,所以访问修饰符不能为private
2.父类虚方法使用的什么访问修饰符,子类重写就必须用什么访问修饰符
案例
using System;
namespace Test1
{
internal class Program
{
static void Main(string[] args)
{
Bird bird = new Sparrow();
bird.Eat();
bird = new Eagle();
bird.Eat();
Console.ReadKey();
}
}
public class Bird
{
//吃
public virtual void Eat()
{
Console.WriteLine("Bird-Eat");
}
}
//麻雀
public class Sparrow : Bird
{
public override void Eat()
{
Console.WriteLine("麻雀吃稻谷");
}
}
//老鹰
public class Eagle : Bird
{
public override void Eat()
{
Console.WriteLine("老鹰吃坤坤");
}
}
}
运行:
麻雀吃稻谷
老鹰吃坤坤
2.抽象类和抽象方法
abstract
修饰符指示被修改内容的实现已丢失或不完整。 abstract 修饰符可用于类、方法、属性、索引和事件。 在类声明中使用 abstract
修饰符来指示某个类仅用作其他类的基类,而不用于自行进行实例化。 标记为抽象的成员必须由派生自抽象类的非抽象类来实现。
抽象类具有以下功能:
-
抽象类不能实例化。
-
抽象类可能包含抽象方法和访问器。
-
无法使用 sealed 修饰符来修改抽象类,因为两个修饰符的含义相反。
sealed
修饰符阻止类被继承,而abstract
修饰符要求类被继承。 -
派生自抽象类的非抽象类,必须包含全部已继承的抽象方法和访问器的实际实现。
在方法或属性声明中使用 abstract
修饰符,以指示该方法或属性不包含实现。
抽象方法具有以下功能:
-
抽象方法是隐式的虚拟方法。
-
只有抽象类中才允许抽象方法声明。
-
由于抽象方法声明不提供实际的实现,因此没有方法主体;方法声明仅以分号结尾,且签名后没有大括号 ({ })。 例如:
public abstract void MyMethod();
实现由方法 override 提供,它是非抽象类的成员。
除了声明和调用语法方面不同外,抽象属性的行为与抽象方法相似。
-
在静态属性上使用
abstract
修饰符是错误的。 -
通过包含使用 override 修饰符的属性声明,可在派生类中重写抽象继承属性。
抽象类和抽象方法在设计模式和框架中用的特别多。
案例
using System;
namespace Test1
{
internal class Program
{
static void Main(string[] args)
{
Bird bird = new Sparrow();
bird.Eat();
bird = new Eagle();
bird.Eat();
Console.ReadKey();
}
}
public abstract class Bird
{
//吃
public abstract void Eat();
//在抽象类中使用普通的方法和字段一样没问题
public void Birdsong()
{
Console.WriteLine("鸟叫");
}
public int Age { get; set; }
}
//麻雀
public class Sparrow : Bird
{
public override void Eat()
{
Console.WriteLine("麻雀吃稻谷");
}
}
//老鹰
public class Eagle : Bird
{
public override void Eat()
{
Console.WriteLine("老鹰吃坤坤");
}
}
}
运行:
麻雀吃稻谷
老鹰吃坤坤
五、interface
接口的使用方法,也可以和多态一样的使用,这就是我为什么在多态的文章中加入了接口,下面是微软官方的一些介绍。
接口可以是命名空间或类的成员。 接口声明可以包含以下成员的声明(没有任何实现的签名):
默认接口成员
上述成员声明通常不包含主体。 从 C# 8.0 开始,接口成员可以声明主体。 接口中的成员主体是默认实现。 具有主体的成员允许接口为不提供重写实现的类和结构提供“默认”实现。 此外,从 C# 8.0 开始,接口可以包括:
- 常量
- 运算符
- 静态构造函数。
- 嵌套类型
- 静态字段、方法、属性、索引和事件
- 使用显式接口实现语法的成员声明。
- 显式访问修饰符(默认访问权限为 public)。
接口的相关内容比较多,可以参考微软的官方文档
在使用上,和上面的虚方法,抽象方法 用起来也差不多,区别是,接口可以继承多个,但子类不能继承多个父类
案例
using System;
namespace Test1
{
internal class Program
{
static void Main(string[] args)
{
Bird bird = new Sparrow();
bird.Eat();
bird = new Eagle();
bird.Eat();
Console.ReadKey();
}
}
public interface Bird
{
//吃
void Eat();
}
//麻雀
public class Sparrow : Bird
{
public void Eat()
{
Console.WriteLine("麻雀吃稻谷");
}
}
//老鹰
public class Eagle : Bird
{
public void Eat()
{
Console.WriteLine("老鹰吃坤坤");
}
}
}
运行:
麻雀吃稻谷
老鹰吃坤坤
结束
如果这个帖子对你有用,欢迎 关注 + 点赞 + 留言,谢谢
end
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)