面向对象进阶 - 继承

20 阅读3分钟

专栏导航

更复杂的结构 - 类与对象

对象的行为 - 对象中的方法

在前面的文章中,我们学习了类和对象的基本概念。这一周,我们将深入探索面向对象编程的强大特性——继承。通过继承,可以复用已有代码,建立类之间的关系,让代码更加优雅和易于维护。

一、什么是继承?

1.1 现实生活中的继承

想象一个家庭关系:

父亲(基类)
  ├── 儿子(派生类)- 继承了父亲的基因
  ├── 女儿(派生类)- 继承了父亲的基因
  └── 其他子女

每个子女都继承父亲的某些特征,同时又有自己的独特之处。

1.2 编程中的继承

继承(Inheritance)是面向对象编程的核心特性之一。它允许一个类(派生类/子类)继承另一个类(基类/父类)的属性和方法。

基类(父类)
  ├── 派生类1(子类)
  ├── 派生类2(子类)
  └── 派生类3(子类)

1.3 为什么要使用继承?

好处:

代码复用:避免重复编写相同的代码
逻辑清晰:建立类之间的层次关系
易于扩展:在基类基础上添加新功能
维护方便:修改基类,所有派生类自动更新

二、基类和派生类

2.1 基本语法

// 基类
class 基类名
{
    // 属性和方法
}

// 派生类:使用 : 继承基类
class 派生类名 : 基类名
{
    // 额外的属性和方法
}

2.2 简单示例:动物类

using System;

namespace Week9Practice
{
    // 基类:动物
    class Animal
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public void Eat()
        {
            Console.WriteLine($"{Name} 正在吃东西...");
        }

        public void Sleep()
        {
            Console.WriteLine($"{Name} 正在睡觉...");
        }

        public void ShowInfo()
        {
            Console.WriteLine($"我是 {Name},今年 {Age} 岁了");
        }
    }

    // 派生类:狗
    class Dog : Animal
    {
        public void Bark()
        {
            Console.WriteLine($"{Name} 汪汪叫!");
        }
    }

    // 派生类:猫
    class Cat : Animal
    {
        public void Meow()
        {
            Console.WriteLine($"{Name} 喵喵叫!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("===== 继承示例 =====\n");

            // 创建狗对象
            Dog dog = new Dog();
            dog.Name = "旺财";
            dog.Age = 3;

            Console.WriteLine("--- 狗的行为 ---");
            dog.ShowInfo();  // 继承自 Animal
            dog.Eat();       // 继承自 Animal
            dog.Sleep();     // 继承自 Animal
            dog.Bark();      // Dog 特有的方法
            Console.WriteLine();

            // 创建猫对象
            Cat cat = new Cat();
            cat.Name = "咪咪";
            cat.Age = 2;

            Console.WriteLine("--- 猫的行为 ---");
            cat.ShowInfo();  // 继承自 Animal
            cat.Eat();       // 继承自 Animal
            cat.Sleep();     // 继承自 Animal
            cat.Meow();      // Cat 特有的方法
        }
    }
}

输出:

===== 继承示例 =====

--- 狗的行为 ---
我是 旺财,今年 3 岁了
旺财 正在吃东西...
旺财 正在睡觉...
旺财 汪汪叫!

--- 猫的行为 ---
我是 咪咪,今年 2 岁了
咪咪 正在吃东西...
咪咪 正在睡觉...
咪咪 喵喵叫!

三、base 关键字

3.1 什么是 base 关键字?

base 关键字用于访问基类的成员。

3.2 使用 base 访问基类成员

class Animal
{
    protected string name;
    
    public Animal(string name)
    {
        this.name = name;
    }
    
    public void Eat()
    {
        Console.WriteLine($"{name} 正在吃东西...");
    }
}

class Dog : Animal
{
    public Dog(string name) : base(name)  // 调用基类构造函数
    {
    }
    
    public void Bark()
    {
        Console.WriteLine($"{name} 汪汪叫!");  // 访问基类的 name
    }
}

3.3 完整示例:车辆类

using System;

namespace Week9Practice
{
    class Vehicle
    {
        protected string brand;
        protected string model;
        protected int year;

        public Vehicle(string brand, string model, int year)
        {
            this.brand = brand;
            this.model = model;
            this.year = year;
        }

        public void Start()
        {
            Console.WriteLine($"{brand} {model} 启动了!");
        }

        public void Stop()
        {
            Console.WriteLine($"{brand} {model} 停止了!");
        }

        public void ShowInfo()
        {
            Console.WriteLine($"品牌:{brand},型号:{model},年份:{year}");
        }
    }

    class Car : Vehicle
    {
        public int NumberOfDoors { get; set; }

        public Car(string brand, string model, int year, int doors)
            : base(brand, model, year)  // 调用基类构造函数
        {
            NumberOfDoors = doors;
        }

        public void Honk()
        {
            Console.WriteLine($"{brand} {model} 嘀嘀叫!");
        }

        // 调用基类方法
        public void ShowDetailedInfo()
        {
            base.ShowInfo();  // 调用基类方法
            Console.WriteLine($"门数:{NumberOfDoors}");
        }
    }

    class Motorcycle : Vehicle
    {
        public Motorcycle(string brand, string model, int year)
            : base(brand, model, year)
        {
        }

        public void RevEngine()
        {
            Console.WriteLine($"{brand} {model} 引擎轰鸣声!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("===== 车辆继承示例 =====\n");

            Car car = new Car("丰田", "卡罗拉", 2020, 4);
            Console.WriteLine("--- 汽车 ---");
            car.ShowDetailedInfo();
            car.Start();
            car.Honk();
            car.Stop();
            Console.WriteLine();

            Motorcycle moto = new Motorcycle("本田", "CBR600RR", 2022);
            Console.WriteLine("--- 摩托车 ---");
            moto.ShowInfo();
            moto.Start();
            moto.RevEngine();
            moto.Stop();
        }
    }
}

输出:

===== 车辆继承示例 =====

--- 汽车 ---
品牌:丰田,型号:卡罗拉,年份:2020
门数:4
丰田 卡罗拉 启动了!
丰田 卡罗拉 嘀嘀叫!
丰田 卡罗拉 停止了!

--- 摩托车 ---
品牌:本田,型号:CBR600RR,年份:2022
本田 CBR600RR 启动了!
本田 CBR600RR 引擎轰鸣声!
本田 CBR600RR 停止了!

四、继承的特点

4.1 单继承

C# 只支持单继承,即一个类只能继承一个基类。

class A { }
class B { }

// ❌ 错误:不能继承多个类
class C : A, B
{
}

// ✅ 正确:只能继承一个类
class C : A
{
}

4.2 继承链

可以形成多层继承链。

class Animal { }           // 基类
class Mammal : Animal { }  // 中间类
class Dog : Mammal { }     // 派生类

4.3 访问修饰符

修饰符访问范围派生类能否访问
public所有地方✅ 能
protected派生类✅ 能
private仅当前类❌ 不能
internal同一程序集✅ 能(如果在同一程序集)

五、方法重写

5.1 虚方法和重写

使用 virtualoverride 关键字实现方法重写。

class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("动物发出声音");
    }
}

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪!");
    }
}

5.2 完整示例

using System;

namespace Week9Practice
{
    class Shape
    {
        public virtual void Draw()
        {
            Console.WriteLine("绘制一个形状");
        }
    }

    class Circle : Shape
    {
        public override void Draw()
        {
            Console.WriteLine("绘制一个圆形");
        }
    }

    class Rectangle : Shape
    {
        public override void Draw()
        {
            Console.WriteLine("绘制一个矩形");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("===== 方法重写示例 =====\n");

            Shape[] shapes = new Shape[3];
            shapes[0] = new Shape();
            shapes[1] = new Circle();
            shapes[2] = new Rectangle();

            foreach (Shape shape in shapes)
            {
                shape.Draw();
            }
        }
    }
}

输出:

===== 方法重写示例 =====

绘制一个形状
绘制一个圆形
绘制一个矩形

六、sealed 关键字

6.1 禁止继承

使用 sealed 关键字可以禁止类被继承。

// 密封类:不能被继承
sealed class FinalClass
{
    public void Method()
    {
        Console.WriteLine("这个类不能被继承");
    }
}

// ❌ 错误:不能继承密封类
// class Derived : FinalClass
// {
// }

6.2 密封方法

可以密封特定方法,禁止它被进一步重写。

class A
{
    public virtual void Method()
    {
        Console.WriteLine("A 的方法");
    }
}

class B : A
{
    public sealed override void Method()  // 密封方法
    {
        Console.WriteLine("B 的方法(不能被重写)");
    }
}

class C : B
{
    // ❌ 错误:不能重写密封方法
    // public override void Method()
    // {
    // }
}

七、实践示例:员工管理系统

using System;
using System.Collections.Generic;

namespace Week9Practice
{
    // 基类:员工
    class Employee
    {
        public string Name { get; set; }
        public int Id { get; set; }
        public double BaseSalary { get; set; }

        public Employee(string name, int id, double baseSalary)
        {
            Name = name;
            Id = id;
            BaseSalary = baseSalary;
        }

        public virtual double CalculateSalary()
        {
            return BaseSalary;
        }

        public virtual void DisplayInfo()
        {
            Console.WriteLine($"ID: {Id}, 姓名: {Name}, 基本工资: {BaseSalary:C}");
        }
    }

    // 派生类:经理
    class Manager : Employee
    {
        public double Bonus { get; set; }

        public Manager(string name, int id, double baseSalary, double bonus)
            : base(name, id, baseSalary)
        {
            Bonus = bonus;
        }

        public override double CalculateSalary()
        {
            return BaseSalary + Bonus;
        }

        public override void DisplayInfo()
        {
            base.DisplayInfo();
            Console.WriteLine($"奖金: {Bonus:C}, 总工资: {CalculateSalary():C}");
            Console.WriteLine("职位: 经理");
        }
    }

    // 派生类:程序员
    class Programmer : Employee
    {
        public int OvertimeHours { get; set; }
        public double HourlyRate { get; set; }

        public Programmer(string name, int id, double baseSalary, int overtimeHours, double hourlyRate)
            : base(name, id, baseSalary)
        {
            OvertimeHours = overtimeHours;
            HourlyRate = hourlyRate;
        }

        public override double CalculateSalary()
        {
            return BaseSalary + (OvertimeHours * HourlyRate);
        }

        public override void DisplayInfo()
        {
            base.DisplayInfo();
            Console.WriteLine($"加班时长: {OvertimeHours}小时, 加班费: {OvertimeHours * HourlyRate:C}");
            Console.WriteLine($"总工资: {CalculateSalary():C}");
            Console.WriteLine("职位: 程序员");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("===== 员工管理系统 =====\n");

            List<Employee> employees = new List<Employee>();

            employees.Add(new Manager("张三", 1001, 15000, 5000));
            employees.Add(new Programmer("李四", 1002, 12000, 20, 100));
            employees.Add(new Programmer("王五", 1003, 11000, 15, 100));
            employees.Add(new Manager("赵六", 1004, 16000, 6000));

            Console.WriteLine("--- 员工信息 ---\n");

            double totalSalary = 0;

            foreach (Employee emp in employees)
            {
                emp.DisplayInfo();
                Console.WriteLine();
                totalSalary += emp.CalculateSalary();
            }

            Console.WriteLine($"=== 工资总额: {totalSalary:C} ===");
        }
    }
}

输出:

===== 员工管理系统 =====

--- 员工信息 ---

ID: 1001, 姓名: 张三, 基本工资: ¥15,000.00
奖金: ¥5,000.00, 总工资: ¥20,000.00
职位: 经理

ID: 1002, 姓名: 李四, 基本工资: ¥12,000.00
加班时长: 20小时, 加班费: ¥2,000.00
总工资: ¥14,000.00
职位: 程序员

ID: 1003, 姓名: 王五, 基本工资: ¥11,000.00
加班时长: 15小时, 加班费: ¥1,500.00
总工资: ¥12,500.00
职位: 程序员

ID: 1004, 姓名: 赵六, 基本工资: ¥16,000.00
奖金: ¥6,000.00, 总工资: ¥22,000.00
职位: 经理

=== 工资总额: ¥68,500.00 ===

八、继承与组合

8.1 什么时候使用继承?

使用继承当:

  • "是"(IS-A)关系:狗一种动物
  • 需要复用大量代码
  • 需要建立清晰的层次结构

8.2 什么时候使用组合?

使用组合当:

  • "有"(HAS-A)关系:汽车一个引擎
  • 只需要某个类的功能,而不是继承它
  • 避免继承带来的复杂性
// 使用组合:汽车有引擎
class Car
{
    private Engine engine;  // 组合:Engine 对象作为成员

    public Car(Engine engine)
    {
        this.engine = engine;
    }

    public void Start()
    {
        engine.Start();  // 使用引擎的功能
    }
}

本章总结

  • ✅ 理解了继承的概念和作用
  • ✅ 学会了使用基类和派生类
  • ✅ 掌握了 base 关键字的使用
  • ✅ 理解了方法重写(virtual 和 override)
  • ✅ 了解了 sealed 关键字的用途
  • ✅ 实践了完整的员工管理系统