目录
C# 多态性
静态多态性
函数重载
运算符重载
动态多态性
virtual 和 abstract
抽象方法和虚方法的区别
重载(overload)和重写(override)
隐藏方法
多态是同一个行为具有多个不同表现形式或形态的能力。
多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。
在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
现实中,比如我们按下 F1 键这个动作:
- 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
- 如果当前在 Word 下弹出的就是 Word 帮助;
- 在 Windows 下弹出的就是 Windows 帮助和支持。
同一个事件发生在不同的对象上会产生不同的结果。
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:
在C#中,函数重载指的是在同一个类中可以定义多个具有相同名称但参数列表不同的函数。通过函数重载,可以根据不同的参数列表来调用合适的函数。
下面是一个简单的示例,演示了如何在C#中使用函数重载:
using System;
public class DateUtils
{
// 函数重载
public string FormatDate(DateTime dt)
{
return dt.ToString("yyyy/MM/dd HH:mm:ss.fff");
}
public string FormatDate(DateTime dt, string format)
{
return dt.ToString(format);
}
}
public class Program
{
public static void Main()
{
DateUtils utils = new DateUtils();
DateTime now = DateTime.Now;
// 使用不同的重载函数进行日期格式化
string formattedDate1 = utils.FormatDate(now); // 使用第一个重载函数
string formattedDate2 = utils.FormatDate(now, "yyyy-MM-dd HH:mm:ss"); // 使用第二个重载函数
// 输出格式化后的日期
Console.WriteLine("格式化日期1: " + formattedDate1);
Console.WriteLine("格式化日期2: " + formattedDate2);
}
}
在上面的示例中,DateUtils 类中定义了两个名为 FormatDate 的函数。第一个函数接受一个 DateTime 类型的参数,并将日期格式化为特定的格式。第二个函数接受一个 DateTime 类型的参数和一个表示日期格式的字符串参数,并根据传入的格式对日期进行格式化。这两个函数拥有相同的名称但参数列表不同。
C#允许我们重定义或重载内置运算符,以便在用户自定义类型上使用它们。运算符重载通过关键字operator后面跟着运算符的符号来定义。
下面是一些常见的运算符重载的示例:
1、算术运算符:
2、比较运算符:
3、逻辑运算符:
4、位运算符:
5、赋值运算符:
这些示例只是一些常见的运算符重载示例,你可以根据自己的需求重载其他运算符。需要注意的是,并非所有运算符都可以被重载,具体可重载的运算符列表请参考C#官方文档。
代码示例
using System;
public class Complex
{
public double Real { get; set; }
public double Imaginary { get; set; }
public Complex(double real, double imaginary)
{
Real = real;
Imaginary = imaginary;
}
// 重载加法运算符 "+"
public static Complex operator +(Complex c1, Complex c2)
{
return new Complex(c1.Real + c2.Real, c1.Imaginary + c2.Imaginary);
}
// 重载减法运算符 "-"
public static Complex operator -(Complex c1, Complex c2)
{
return new Complex(c1.Real - c2.Real, c1.Imaginary - c2.Imaginary);
}
// 重载乘法运算符 "*"
public static Complex operator *(Complex c1, Complex c2)
{
double real = c1.Real * c2.Real - c1.Imaginary * c2.Imaginary;
double imaginary = c1.Real * c2.Imaginary + c1.Imaginary * c2.Real;
return new Complex(real, imaginary);
}
// 重载复数的输出格式
public override string ToString()
{
return $"{Real} + {Imaginary}i";
}
}
class Program
{
static void Main()
{
Complex c1 = new Complex(1, 2);
Complex c2 = new Complex(3, 4);
Complex sum = c1 + c2; // 使用重载的加法运算符
Console.WriteLine($"总和: {sum}");
Complex difference = c1 - c2; // 使用重载的减法运算符
Console.WriteLine($"差别: {difference}");
Complex product = c1 * c2; // 使用重载的乘法运算符
Console.WriteLine($"相乘: {product}");
}
}
当上面的代码被编译和执行时,它会产生下列结果:
总和: 4 + 6i
差别: -2 + -2i
相乘: -5 + 10i
在C#中,使用抽象类和虚方法可以实现动态多态性。
C# 允许您使用关键字 abstract 创建抽象类,用于提供接口的部分类的实现。当一个派生类继承自该抽象类时,实现即完成。抽象类包含抽象方法,抽象方法可被派生类实现。派生类具有更专业的功能。
请注意,下面是有关抽象类的一些规则:
下面的程序演示了一个抽象类:
using System;
// 定义抽象类
public abstract class Shape
{
public string Name { get; set; }
// 定义抽象方法
public abstract double CalculateArea();
}
// 继承自抽象类的具体类:圆形
public class Circle : Shape
{
public double Radius { get; set; }
// 实现抽象方法
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
// 继承自抽象类的具体类:正方形
public class Square : Shape
{
public double SideLength { get; set; }
// 实现抽象方法
public override double CalculateArea()
{
return SideLength * SideLength;
}
}
class Program
{
static void Main()
{
Circle circle = new Circle { Name = "圆形", Radius = 3 };
Console.WriteLine("圆形的面积: " + circle.CalculateArea()); // 输出结果:"圆形的面积: 28.274333882308138"
Square square = new Square { Name = "正方形", SideLength = 4 };
Console.WriteLine("正方形的面积: " + square.CalculateArea()); // 输出结果:"正方形的面积: 16"
}
}
另外,虚方法是在基类中,可以使用关键字virtual来声明一个方法为虚方法,表示它可以被派生类重写。而在派生类中,可以使用关键字override来重写基类中的虚方法。
通过使用抽象类和虚方法,可以实现代码的可扩展性和灵活性。抽象类提供了一种统一的接口,定义了需要实现的方法,而具体的实现则由派生类来完成。虚方法允许在派生类中重写方法,实现不同的行为。
以下是一个简单的代码示例,演示了如何使用抽象类和虚方法来实现动态多态性。
using System;
// 定义一个抽象类
public abstract class Shape
{
// 定义一个虚方法
public virtual double CalculateArea()
{
return 0;
}
}
// 派生类1:矩形
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
// 实现抽象方法
public override double CalculateArea()
{
return Width * Height;
}
}
// 派生类2:圆形
public class Circle : Shape
{
public double Radius { get; set; }
// 实现抽象方法
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
class Program
{
static void Main()
{
// 创建一个矩形对象
Shape rect = new Rectangle { Width = 5, Height = 10 };
Console.WriteLine("矩形的面积:" + rect.CalculateArea());
// 创建一个圆形对象
Shape circle = new Circle { Radius = 3 };
Console.WriteLine("圆形的面积:" + circle.CalculateArea());
}
}
当上面的代码被编译和执行时,它会产生下列结果:
矩形的面积:50
圆形的面积:28.274333882308138
需要注意的是,抽象方法只能存在于抽象类中,而不是普通的类。如果一个类包含抽象方法,则该类本身必须被声明为抽象类。而虚方法可以存在于任何类中,无论是抽象类还是普通类。
总之,virtual和abstract关键字都用于实现多态性,在派生类中重新定义父类的方法。virtual方法允许派生类选择重写并提供新的实现,而abstract方法则要求派生类必须提供具体的实现。另外,抽象类本身不能被实例化,只能作为其他类的基类使用。
1、重载(overload): 在同一个作用域(一般指一个类)的两个或多个方法函数名相同,参数列表不同的方法叫做重载,它们有三个特点(俗称两必须一可以):
2、重写(override):子类中为满足自己的需要来重复定义某个方法的不同实现,需要用 override 关键字,被重写的方法必须是虚方法,用的是 virtual 关键字。它的特点是(三个相同):
代码示例
using System;
public class Calculator
{
// 方法重载,参数列表不同
public int Add(int num1, int num2)
{
return num1 + num2;
}
public double Add(double num1, double num2)
{
return num1 + num2;
}
}
public class Shape
{
// 虚方法
public virtual void Draw()
{
Console.WriteLine("正在绘制形状。。。");
}
}
public class Circle : Shape
{
// 重写基类的虚方法
public override void Draw()
{
Console.WriteLine("正在绘制圆。。。");
}
}
class Program
{
static void Main()
{
Calculator calculator = new Calculator();
int result1 = calculator.Add(3, 4); // 调用第一个Add方法
double result2 = calculator.Add(2.5, 3.7); // 调用第二个Add方法
Console.WriteLine("int加法的结果: " + result1); // 输出结果:"int加法的结果: 7"
Console.WriteLine("double加法的结果: " + result2); // 输出结果:"double加法的结果: 6.2"
Shape shape1 = new Shape();
shape1.Draw(); // 输出结果:"正在绘制形状。。。"
Shape shape2 = new Circle();
shape2.Draw(); // 输出结果:"正在绘制圆。。。"
}
}
当在派生类中定义一个与基类中同名的方法时,会发生方法隐藏。使用关键字 new 可以实现方法隐藏。
using System;
public class Shape
{
public void Draw()
{
Console.WriteLine("正在绘制形状。。。");
}
}
public class Circle : Shape
{
public new void Draw()
{
Console.WriteLine("正在绘制圆。。。");
}
}
class Program
{
static void Main()
{
Shape shape1 = new Shape();
shape1.Draw(); // 输出结果:"正在绘制形状。。。"
Shape shape2 = new Circle();
shape2.Draw(); // 输出结果:"正在绘制形状。。。"(调用的是基类的方法)
Circle circle = new Circle();
circle.Draw(); // 输出结果:"正在绘制圆。。。"
}
}
在上述代码中,Shape 类定义了一个 Draw() 方法,Circle 类继承自 Shape 并定义了一个同名的 Draw() 方法并使用 new 关键字进行方法隐藏。
在 Main() 方法中,首先创建了一个 Shape 对象 shape1,调用其 Draw() 方法,输出结果为 "正在绘制形状。。。"。接着创建了一个 Circle 对象 shape2,将其赋值给 Shape 类型的变量 shape2,再次调用 shape2.Draw() 方法,输出结果仍然为 ""正在绘制形状。。。"",这是因为变量的静态类型是 Shape,所以调用的是基类的方法。最后,创建了一个 Circle 对象 circle,直接调用其 Draw() 方法,输出结果为 "正在绘制圆。。。",这是因为直接通过派生类的实例调用方法时,会调用派生类中隐藏的方法。
需要注意的是,方法隐藏并不是方法重写。如果想要实现方法的多态性,应该使用方法重写(override)而不是隐藏(new)。