继承与多态
这是什么:继承让子类白拿父类的能力,多态让同一句代码对不同对象有不同表现。
为什么用:狗会叫、猫会叫,写两个类各抄一遍 Speak?重复劳动,改一处还得记得改另一处。有了这两个机制,公共能力只写一次,之后各自认领自己的活。
如何用:本章先写 class Dog : Animal 的继承,再让 Animal a = new Dog(...) 演示多态。
冒号继承:class Dog : Animal
冒号 : 就是"继承":右边是父类(基类),左边是子类(派生类)。子类自动拥有父类的字段、属性和方法,还能再加自己的。
Animal a = new Animal("神秘生物");
a.Speak();
Dog d = new Dog("旺财");
d.Speak(); // 名字继承自父类,叫法是自己的
class Animal
{
public string Name { get; set; }
public Animal(string name) => Name = name;
public virtual void Speak() // virtual:允许子类改写
{
Console.WriteLine($"{Name}发出了神秘的声音");
}
}
class Dog : Animal // Dog 是一种 Animal
{
public Dog(string name) : base(name) { } // base(name):把名字交给父类构造函数
public override void Speak() // override:改写父类方法
{
Console.WriteLine($"{Name}:汪汪!");
}
}
运行输出:
神秘生物发出了神秘的声音
旺财:汪汪!
| 关键字 | 写在哪 | 作用 |
|---|---|---|
: |
类名后 | 继承:class Dog : Animal |
virtual |
父类方法 | 标记"允许子类改写" |
override |
子类方法 | 改写父类的 virtual 方法 |
base |
子类里 | 指向父类:base(name) 调父类构造函数,base.Speak() 调父类方法 |
多态:一句 a.Speak(),各叫各的
多态的打开方式:用父类变量装子类对象,调方法时自动执行子类的版本。
List<Animal> zoo = new List<Animal>
{
new Dog("旺财"),
new Dog("大黄"),
new Animal("神秘生物")
};
foreach (Animal a in zoo)
{
a.Speak(); // 声明是 Animal,跑起来各是各的
}
运行输出:
旺财:汪汪!
大黄:汪汪!
神秘生物发出了神秘的声音
循环里只有一句 a.Speak(),C# 却分得清哪只是狗、哪只是普通动物——一个方法,多种形态,所以叫多态。以后加个 Cat 类,这个循环一个字都不用改。
再加一个子类:Cat
验证多态真正的威力——加新子类,旧代码不用改:
class Cat : Animal
{
public Cat(string name) : base(name) { }
public override void Speak()
{
Console.WriteLine($"{Name}:喵~");
}
}
// 用法
List<Animal> zoo = new List<Animal>
{
new Dog("旺财"),
new Cat("咪咪"),
new Animal("神秘生物")
};
foreach (Animal a in zoo)
{
a.Speak();
}
输出:
旺财:汪汪!
咪咪:喵~
神秘生物发出了神秘的声音
记忆口诀
冒号继承爹的家产,virtual 开口 override 接管;父类变量装子类,一句调用百样声。
❓ 常见问题
Q1:子类必须调用 base 构造函数吗?
如果父类只有带参数的构造函数(如 public Animal(string name)),子类必须用 base(name) 调用,否则编译报错。如果父类有无参构造函数,子类可以省略 base,编译器会自动调。
Q2:virtual 和 override 必须成对出现吗?
是的。父类方法要允许被改写,加 virtual;子类要改写,加 override。少了任何一端,编译器都会报错。
Q3:子类能继承多个父类吗?
不能。C# 类只能继承一个父类(单继承)。如果需要多个"能力",用接口(下一章)。
⚠️ 新手翻车现场
翻车一:子类方法忘了加 override
class Dog : Animal
{
public void Speak() // ❌ 没加 override,只是"隐藏"父类方法,会有编译警告
{
Console.WriteLine($"{Name}:汪汪!");
}
}
问题:没有 override,这不是真正的多态,编译器会发出警告说"隐藏了继承的成员",多态行为不会按预期工作。
修正:加上 public override void Speak()。
翻车二:父类方法没加 virtual,子类强行 override
class Animal
{
public void Speak() { ... } // 没 virtual
}
class Dog : Animal
{
public override void Speak() { ... } // ❌ 编译报错:父类方法不是 virtual
}
问题:只有标了 virtual 的父类方法,子类才能 override。
修正:给父类方法加 virtual,或者子类不加 override(但那就不是多态了)。
翻车三:base 用错地方
class Dog : Animal
{
public Dog(string name) : base() { } // ❌ 父类只有带参构造函数,base() 不匹配
}
问题:base() 调用父类无参构造函数,但父类没有。
修正:base(name) 把名字传给父类构造函数。
🎯 什么时候用继承/多态
| 场景 | 推荐 | 原因 |
|---|---|---|
| 多个类共享公共数据和行为 | 继承 | 公共代码只写一次放在父类 |
| 同一方法,不同对象表现不同 | 多态 | virtual/override,父类变量装子类 |
| 不想改旧代码就扩展新功能 | 多态 | 新增子类,旧循环代码一个字不用改 |
📝 本章小结
class 子类 : 父类实现继承,子类自动拥有父类成员virtual标记父类可改写方法,override在子类中改写base指向父类,用于调用父类构造函数和方法- 多态:父类变量装子类对象,调方法时自动执行子类版本
- C# 单继承(一个类只能继承一个父类)
- 记忆口诀:冒号继承爹的家产,virtual 开口 override 接管;父类变量装子类,一句调用百样声。
动手改
- 新建
Cat类继承Animal,override 出"喵~",加进 zoo 验证循环不用改 - 给
Animal加public virtual void Eat(),在Dog里 override,并先用base.Eat()调一次父类版本 - 把
Animal.Speak的virtual删掉再编译,看看编译器会怎么提醒你
下一章:接口,签一份能力合同 🐸