文章大纲
加载中...

C#教程3:继承、重载与多态

3/15/2025 361 阅读
C#教程3:继承、重载与多态

继承 Inheritance

假设我们想设计一个狗的类,它需要包含Color和Species属性,以及Eat()和Sleep()方法,我们通常会这样做:

public class Dog
{
    //1.属性,颜色和品种
    public string Color { get; set; } 
    public string Species { get; set; } 
    //2.构造函数
    public Dog(string color, string species)
    {
        Color = color;
        Species = species;
    }
    //方法
    public void Eat()
    {
        Console.WriteLine($"我是一只{Color}的{Species},我在吃东西!");
    }
    public void Sleep()
    {
        Console.WriteLine($"我是一只{Color}的{Species},我在睡觉!");
    }
}

现在,如果我们想设计一个类似的狼的类,我们大概率也会这么做:

public class Wolf
{
    //属性,颜色和品种
    public string Color { get; set; } 
    public string Species { get; set; } 
    //构造函数
    public Wolf(string color, string species)
    {
        Color = color;
        Species = species;
    }
    //方法
    public void Eat()
    {
        Console.WriteLine($"我是一只{Color}的{Species},我在吃东西!");
    }
    public void Sleep()
    {
        Console.WriteLine($"我是一只{Color}的{Species},我在睡觉!");
    }
}

​ 对比上面两段代码,我们发现,大部分代码是一样的;不一样的地方仅仅是类的名字(和构造函数的名字);如果下次我们再设计一个老虎类,同样的代码又要再写一次…如果这样的话,重复的代码我们写了又写,我们写的代码将非常的臃肿无聊!那么,怎么解决这个问题呢?

​ 我们发现我们写的属性和方法基本上所有动物都具备,因此我们将这些所有动物共有的属性和方法封装到一个动物类(Animal)里面。

public class Animal
{
    public string Color { get; set; } 
    public string Species { get; set; } 
    //构造函数
    public Animal(string color, string species)
    {
        Color = color;
        Species = species;
    }
    public void Eat()
    {
        Console.WriteLine($"我是一只{Color}的{Species},我在吃东西!");
    }
    public void Sleep()
    {
        Console.WriteLine($"我是一只{Color}的{Species},我在睡觉!");
    }
}

有了这个Animal类,我们可以使用继承复用这些公用的属性和方法(代码)了

继承建立了类之间的层次结构,反映了现实世界中的"是一种"关系。

继承的语法如下:

public class 子类名 : 父类名
{
    
}

首先,创建一个Dog类,继承自Animal类

public class Dog : Animal
{
    //狗的构造函数:base(color, species)表示调用父类的构造函数
    public Dog(string color, string species) : base(color, species)
    {
        
    }
}

然后,创建一个Wolf类,同样继承自Animal类

public class Wolf : Animal
{
    //狼的构造函数:base(color, species)表示调用父类的构造函数
    public Wolf(string color, string species) : base(color, species)
    {
        
    }
}

虽然Dog类和Wolf类都内部只写了一个构造函数,但是由于它们都继承自Animal,因此Animal中非私有的属性和方法这两个子类都可以使用。

//创建一个Dog类的对象
Dog dog1 = new Dog("黄色", "狗", "旺财");
//由于Dog类继承自Animal类,因此Dog类生成的对象可以访问Animal类中的属性
Console.WriteLine($"我是一只{dog1.Color}的{dog1.Species}!");
//也可以调用Animal类中的方法  
dog1.Eat();
//创建一个Wolf类的对象
Wolf wolf1 = new Wolf("黑色", "狼",false);
//由于Wolf类也继承自Animal类,因此Wolf类生成的对象也可以访问Animal类中的属性
Console.WriteLine($"我是一只{wolf1.Color}的{wolf1.Species}!");
//也可以调用Animal类中的方法
wolf1.Eat();

如果后续我们还想创建老虎类、大象类和其他动物类,我们同样只需要让它们继承Animal类即可。

如此,我们通过继承提高了代码的复用性!

再思考一下,不同动物有不同的属性,比如,如狗一般有名字,而狼没有;不同的动物可以做不同的事情(方法),比如狼会捕猎,而狗不会。

因此,我们在继承Animal类后,我们可以也应对Dog类和Wolf类做进一步的设计,使其具备一些其他类所不具备的属性或方法。我们把这个称之为扩展。

接下来,我们来扩展设计一下我们的Dog类,使其具有一些狗所特有的属性或方法:

public class Dog : Animal
{
    //狗特有的属性:名字
    public string Name { get; set; } 
    //狗的构造函数:base(color, species)表示调用父类的构造函数
    public Dog(string color, string species, string name) : base(color, species)
    {
        Name = name;
    }
    //狗特有的属性:名字
    public void Bark()//狗特有的方法:汪汪叫
    {
        Console.WriteLine("我是一只狗,我会汪汪叫!");
    }
}

也将Wolf类修改一下:

public class Wolf : Animal
{
    //狼特有的属性:是否是群体的领头
    public bool IsAlpha { get; set; }
    //狼的构造函数。base(color, species)表示调用父类的构造函数
    public Wolf(string color, string species,bool isAlpha) : base(color, species)
    {
        IsAlpha = isAlpha;
    }
    //狼特有的方法:捕猎
    public void Hunt()
    {
        Console.WriteLine("我是一只狼,我会捕猎!");
    }
}

改成这样之后,我们实列化的Dog对象都有Name属性和Bark方法,而实例化的Wolf对象都有IsAlpha属性和Hunt方法。

//狗有自己的属性
Console.WriteLine($"我的名字叫{dog1.Name}");
//也有自己的方法
dog1.Bark();
//狼也有自己的属性
Console.WriteLine($"我是群体的领头吗?{wolf1.IsAlpha}");
//狼也有自己的方法
wolf1.Hunt();
//错误,狗没有捕猎的方法
dog1.Hunt();
//错误,狼没有汪汪叫的方法
wolf1.Bark();

这样的话,我们设计的Dog和Wolf既利用继承复用了Animal父类的属性和方法(代码),又扩展出了自己独特的属性和方法(代码)。

继承可以是多层次的,比如,我们可以让Dog类也派生出不同的子类:

//哈士奇
public class Husky:Dog
{
    
}
//柯基
public class Corgi:Dog
{
}
//泰迪
public class Teddy:Dog
{

}

同样,我们也可以让Wolf派生出不同的子类

//灰狼
public class GreyWolf:Wolf
{
    
}
//平原狼
public class PlainsWolf:Wolf
{
}
//森林狼
public class TimberWolf:Wolf
{

}

我们可以用UML类图来表示类的层级结构

最上层类Animal是所有动物的基类,它用于定义所有动物公用的属性和方法;

狗继承自Animal,它是一种Animal,它有Animal的属性和方法,也有自己的属性和方法;

狼也继承自Animal,它也是一种Animal,它有Animal的属性和方法,也有自己的属性和方法;

狗和狼都是Animal的子类;

狗和狼又都有自己的子类;

通过这种层级继承的结构,我们模仿显示世界中**“是一种”**的逻辑,代码看起来清晰易懂,也极大的提高了代码的复用性,

重载 Overloading

​ 继续看上面的例子,我们在Animal类中定义了吃的方法,继承该类的所有子类均使用这个方法,所以狼和狗吃的方法都是一样的,然而,这与实际的情况不符合!狗是家养动物,是杂食性的,而狼是野生动物,是肉食性的,它们吃的方法不应该一样。

​ 我们可以利用重载来解决这个问题,首先在Animal的Eat()方法上添加virtual关键字,把它变成虚函数,虚函数运行后代将其重写。

public class Animal
{
    public string Color { get; set; } 
    public string Species { get; set; } 
    //构造函数
    public Animal(string color, string species)
    {
        Color = color;
        Species = species;
    }
    public virtual void Eat()
    {
        Console.WriteLine($"我是一只{Color}的{Species},我在吃东西!");
    }
    public void Sleep()
    {
        Console.WriteLine($"我是一只{Color}的{Species},我在睡觉!");
    }
}

接着,我们在Dog类中利用override关键字重写一些Dog类版本的Eat()方法

public class Dog : Animal
{
    //狗特有的属性:名字
    public string Name { get; set; } 
    //狗的构造函数:base(color, species)表示调用父类的构造函数
    public Dog(string color, string species, string name) : base(color, species)
    {
        Name = name;
    }
    //狗特有的属性:名字
    public void Bark()//狗特有的方法:汪汪叫
    {
        Console.WriteLine("我是一只狗,我会汪汪叫!");
    }
    //重写Eat方法
    public override void Eat()
    {
        Console.WriteLine($"我是一只{Color}的{Species},我在吃骨头!");
    }
}

然后,我们在Wolf类中同样利用override关键字重写一些Wolf类版本的Eat()方法

public class Wolf : Animal
{
    //狼特有的属性:是否是群体的领头
    public bool IsAlpha { get; set; }
    //狼的构造函数。base(color, species)表示调用父类的构造函数
    public Wolf(string color, string species,bool isAlpha) : base(color, species)
    {
        IsAlpha = isAlpha;
    }
    //狼特有的方法:捕猎
    public void Hunt()
    {
        Console.WriteLine("我是一只狼,我会捕猎!");
    }
    //重写Eat方法
    public override void Eat()
    {
        Console.WriteLine($"我是一只{Color}的{Species},我在吃小兔子!");
    }
}

接下来,我们看到,同样是调用Eat()方法,狗和狼都调用了自己的版本,而不是Animal父类中定义的版本

//狗吃的方法,重写了父类,用自己的方法
dog1.Eat();
//狼吃的方法,重写了父类,用自己的方法
wolf1.Eat();

多态 Polymorphism

利用重载,我们可以实现多态。

什么是多态?同一种对象,调用同一个方法,但是运行的版本各不一样。

//因为Dog继承自Animal,所以我们在创建Dog对象时,把它声明为Animal类型
Animal dog2 = new Dog("黑色", "狗", "小黑");
//类似的,因为Wolf继承自Animal,所以我们在创建Wof对象时,也可以把它声明为Animal类型
Animal wolf2 = new Wolf("白色", "狼", false);
//接下来,我们创建一个Animal类型的列表
List<Animal> animals = new List<Animal>();
//然后把刚才创建的两个动物(狗dog2和狼wolf2)添加进行
animals.Add(dog2);
animals.Add(wolf2);
//最后,我们循环遍历这些动物
foreach(var animal in animals)
{
    //让他们都运行Eat方法
    //通过输出,我们可以看到,同样的一行代码,输出却不一样,这就是多态!
    animal.Eat();
}

作业

作业一:交通工具系统

设计一个简单的交通工具类层次结构,要求如下:

  1. 创建一个基类 Vehicle(交通工具),包含以下成员:

    • 属性:品牌(Brand)、型号(Model)、最高速度(MaxSpeed)
    • 方法:Start()Stop()Move()(设为虚方法)
  2. 创建至少三个子类:Car(汽车)、Bicycle(自行车)、Boat(船),它们都继承自 Vehicle

    • 每个子类添加特有的属性(如 Car 可以有 NumberOfDoors
    • 每个子类重写 Move() 方法,实现自己的移动方式
    • 每个子类添加一个特有的方法(如 Car 可以有 Honk()
  3. Main 方法中:

    • 创建各种交通工具的对象
    • 调用它们的方法展示继承和多态
    • 创建一个 Vehicle 类型的数组,存储不同的交通工具对象,并循环调用它们的方法

作业二:形状计算器

设计一个形状计算器,用于计算不同形状的面积和周长,要求如下:

  1. 创建一个抽象基类 Shape(形状),包含以下成员:

    • 属性:颜色(Color)
    • 抽象方法:CalculateArea()CalculatePerimeter()
    • 普通方法:DisplayInfo(),用于显示形状的信息
  2. 创建至少三个子类:Circle(圆)、Rectangle(矩形)、Triangle(三角形),它们都继承自 Shape

    • 每个子类添加必要的属性(如 Circle 需要 Radius
    • 每个子类实现抽象方法 CalculateArea()CalculatePerimeter(),计算各自的面积和周长
    • 每个子类可以重写 DisplayInfo() 方法,添加特有的信息
  3. Main 方法中:

    • 创建各种形状的对象
    • 计算并显示它们的面积和周长
    • 创建一个 Shape 类型的数组,存储不同的形状对象,并循环计算它们的面积和周长

评论 (0)

暂无评论,来发表第一条评论吧!