🐸 青蛙编程网
首页 / C# / 面向对象 / 3. 继承与多态

继承与多态

这是什么:继承让子类白拿父类的能力,多态让同一句代码对不同对象有不同表现。

为什么用:狗会叫、猫会叫,写两个类各抄一遍 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 接管;父类变量装子类,一句调用百样声。

动手改

  1. 新建 Cat 类继承 Animal,override 出"喵~",加进 zoo 验证循环不用改
  2. 给 Animal 加 public virtual void Eat(),在 Dog 里 override,并先用 base.Eat() 调一次父类版本
  3. 把 Animal.Speak 的 virtual 删掉再编译,看看编译器会怎么提醒你

下一章:接口,签一份能力合同 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。