专栏导航
在前面的文章中,我们学习了类和对象的基本概念。这一周,我们将深入探索面向对象编程的强大特性——继承。通过继承,可以复用已有代码,建立类之间的关系,让代码更加优雅和易于维护。
一、什么是继承?
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 虚方法和重写
使用 virtual 和 override 关键字实现方法重写。
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 关键字的用途
- ✅ 实践了完整的员工管理系统