继承与多态
什么是继承
在C#中,继承是面向对象编程的核心概念,它允许创建一个类继承另一个类的成员(方法、属性、字段和事件)。
提示
继承促进了代码的重用,并能建立类之间的层次关系。
基本概念:
- 基类(父类):被继承的类。
- 派生类(子类):继承基类的类。
特点
- 代码重用:派生类可以重用基类中的代码,减少重复代码。
- 多态性:通过继承,可以实现方法的重写(Override)和重载(Overload),使得同一个方法在基类和派生类中有不同的实现。
- 封装:继承允许隐藏复杂的实现细节,只暴露必要的接口给派生类。
定义语法
class BaseClass
{
// 基类成员
}
class DerivedClass : BaseClass
{
// 派生类成员
}
示例
假设我们有一个Vehicle
基类和两个派生类Car
和Truck
:
public class Vehicle
{
public string Brand { get; set; }
public void Honk()
{
Console.WriteLine("Beep!");
}
}
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
}
public class Truck : Vehicle
{
public int LoadCapacity { get; set; }
}
在这里,Car
和Truck
类继承了Vehicle
类,它们可以使用Vehicle
类中定义的Brand
属性和Honk
方法,同时还可以添加它们自己的特有属性,如NumberOfDoors
和LoadCapacity
。
注意事项
C#不支持多重继承
在C#中,一个类只能直接继承自一个基类。
但是,可以通过接口实现多重继承的效果。
也就是说,一个类可以继承多个接口,但只能继承自一个类。
interface IDriveable
{
void Drive();
}
interface IFlyable
{
void Fly();
}
class FlyingCar : IDriveable, IFlyable
{
public void Drive()
{
Console.WriteLine("Driving on the road.");
}
public void Fly()
{
Console.WriteLine("Flying in the sky.");
}
}
public class Program
{
static void Main(string[] args)
{
FlyingCar myFlyingCar = new FlyingCar();
myFlyingCar.Drive(); // Driving on the road.
myFlyingCar.Fly(); // Flying in the sky.
}
}
不能继承基类的构造函数
派生类不能直接继承基类的构造函数,但是,可以通过base
关键字调用基类的构造函数。
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
// 基类构造函数
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
public class Employee : Person
{
public string Position { get; set; }
// 派生类构造函数,使用base关键字调用基类构造函数
public Employee(string name, int age, string position) : base(name, age)
{
Position = position;
}
}
class Program
{
static void Main(string[] args)
{
Employee employee = new Employee("John Doe", 30, "Software Developer");
Console.WriteLine($"Name: {employee.Name}, Age: {employee.Age}, Position: {employee.Position}");
// 运行结果:
// Name: John Doe, Age: 30, Position: Software Developer
}
}
访问限制
派生类可以访问基类中所有的public
和protected
成员,但不能直接访问private
成员。
接口继承
C# 接口支持接口继承,即一个接口可以继承自一个或多个其他接口。接口继承允许派生接口重用基接口的定义,并可以扩展基接口的成员列表。
interface DerivedInterface : BaseInterface1, BaseInterface2, ...
{
// 派生接口的成员
}
其中:
DerivedInterface
:派生接口名称BaseInterface1
、BaseInterface2
:基接口名称
比如,以下代码定义了一个名为 Shape
的基接口,用于表示形状,并定义了 Area()
和 Perimeter()
两个方法:
interface Shape
{
double Area();
double Perimeter();
}
然后,定义了一个名为 Circle
的派生接口,继承自 Shape
接口,并新增了一个 Radius
属性:
interface Circle : Shape
{
double Radius { get; set; }
}
接口继承具有以下特点:
- 继承性: 派生接口继承了基接口的所有成员,包括方法、属性、事件等。
- 扩展性: 派生接口可以扩展基接口的成员列表,但不能改变基接口成员的定义。
- 多继承: 一个接口可以继承自多个基接口。
多态
提示
多态也是面向对象编程中的核心概念,它允许对象以引用自己或基类的方式被视为是其他类型的实例。
多态性允许同一个接口或基类引用多种不同的派生类对象,而这些对象可以以不同的方式响应相同的消息(方法调用),这增加了程序的灵活性和可扩展性。
多态可以分为两种类型:
- 编译时多态:也称为静态多态,主要通过方法重载(Method Overloading)和运算符重载(Operator Overloading)实现。
- 运行时多态:也称为动态多态,通过覆盖(Overriding)实现。
编译时多态
编译时多态,也称为静态多态,是指在编译时(而不是运行时)确定方法调用的具体版本的能力。
在C#中,编译时多态主要通过两种方式实现:方法重载和运算符重载。
方法重载(Method Overloading)和是指在同一个类中定义多个名称相同但参数列表不同(参数的类型、个数或顺序不同)的方法。编译器根据方法被调用时提供的参数类型和数量,决定调用哪个版本的方法。
public class Calculator
{
// 第一个Add方法,接受两个int类型的参数
public int Add(int a, int b)
{
return a + b;
}
// 重载Add方法,接受三个int类型的参数
public int Add(int a, int b, int c)
{
return a + b + c;
}
// 再次重载Add方法,接受两个double类型的参数
public double Add(double a, double b)
{
return a + b;
}
}
在这个例子中,Add
方法被重载了三次,编译器会根据调用Add
方法时提供的参数类型和数量,决定调用哪个版本。
运算符重载(Operator Overloading)允许为现有的C#运算符提供自定义的实现,使得这些运算符可以用于自定义类型的操作。运算符重载通过在类或结构中定义静态方法来实现,方法名由operator
关键字和要重载的运算符组成。
public class Complex
{
public int Real { get; set; }
public int Imaginary { get; set; }
public Complex(int real, int imaginary)
{
Real = real;
Imaginary = imaginary;
}
// 重载加法运算符
public static Complex operator +(Complex a, Complex b)
{
return new Complex(a.Real + b.Real, a.Imaginary + b.Imaginary);
}
}
在这个例子中,为Complex
类重载了加法运算符+
,使得可以直接使用加法运算符来计算两个Complex
对象的和。
运行时多态
提示
运行时多态是通过使用虚方法(virtual methods)和抽象方法(abstract methods)在基类中定义,然后在派生类中重写(override)这些方法来实现的。这种方式允许在运行时根据对象的实际类型调用相应的方法,而不是在编译时确定。
虚方法是在基类中使用virtual
关键字声明的方法。虚方法在基类中提供了一个默认的实现,但派生类可以选择是否根据自己的需要重写这个方法。派生类使用override
关键字来重写基类中的虚方法。
抽象方法是在抽象类中声明的,使用abstract
关键字。抽象方法没有具体的实现,在抽象类中只提供方法的声明。任何继承抽象类的派生类都必须实现其所有的抽象方法,除非派生类也是抽象类。派生类必须使用override
关键字来实现所有的抽象方法。
示例:
基类定义了一个虚方法:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Some generic animal sound");
}
}
派生类重写了基类的方法:
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof");
}
}
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Meow");
}
}
使用多态性:
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.Speak(); // 输出:Woof
myCat.Speak(); // 输出:Meow
在上述示例中,尽管myDog
和myCat
都是Animal
类型的引用,但它们分别指向Dog
和Cat
的实例。在运行时,根据实际指向的对象类型调用相应的Speak
方法,展现了多态性。
运算符重载
上面提到了,我们可以通过关键字 operator 后跟运算符的符号来重载运算符。运算符重载允许你为自定义类型(类或结构)定义新的行为,以便使用标准的运算符(如+
、-
、*
、/
等)进行操作。通过运算符重载,可以使自定义类型的实例像内置类型一样进行操作。
运算符重载规则:
- 必须在类或结构体内定义:运算符重载必须作为类或结构体的静态方法进行定义。
- 必须有一个关键字
operator
:方法签名中必须包含关键字operator
,后跟要重载的运算符符号。 - 参数和返回类型:参数必须是类或结构体类型,返回类型可以是任何类型,通常是类或结构体类型。
可以被重载的运算符包括:
- 一元运算符:
+
,-
,!
,~
,++
,--
- 二元运算符:
+
,-
,*
,/
,%
- 比较运算符:
==
,!=
,<
,>
,<=
,>=
不能被重载的运算符包括:
- 条件逻辑运算符:
&&
,||
- 赋值运算符:
+=
,-=
,*=
,/=
,%=
- 其他运算符:
=
,.
,?:
,->
,new
,is
,sizeof
,typeof
注意,在重载相等运算符时,通常还需要重写 Equals
方法和 GetHashCode
方法。
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
return false;
Complex c = (Complex)obj;
return Real == c.Real && Imaginary == c.Imaginary;
}
public override int GetHashCode()
{
return Real.GetHashCode() ^ Imaginary.GetHashCode();
}
public static bool operator ==(Complex c1, Complex c2)
{
if (ReferenceEquals(c1, null))
return ReferenceEquals(c2, null);
return c1.Equals(c2);
}
public static bool operator !=(Complex c1, Complex c2)
{
return !(c1 == c2);
}