概念: 使用 `abstract` 关键字修饰的类,叫做==抽象类== 使用 `abstract` 关键字修饰的方法,叫做==抽象方法== 简单理解: 抽象类像一份“不完整的父类模板” 它可以规定子类必须有什么能力,但具体怎么做,交给子类自己实现 补充: 有些教程会把“抽象方法”叫作“抽象函数”,初学阶段可以先理解成同一个意思 通常用法: 1. 多个类有共同身份,但父类本身不应该被直接创建对象 2. 父类能提供一部分公共代码 3. 父类还想强制子类必须实现某些方法 4. 配合[[继承]]和[[多态vob|多态]],让调用方通过父类统一调用子类行为 ---- # 语法 抽象类: ```csharp abstract class 类名 { } ``` 抽象方法: ```csharp abstract class 类名 { public abstract void 方法名(); } ``` 子类实现抽象方法: ```csharp class 子类名 : 抽象类名 { public override void 方法名() { } } ``` 注意: 抽象方法没有方法体,结尾直接写分号 `;` ---- # 基础案例 ```csharp abstract class Animal { public string Name; public Animal(string name) { Name = name; } public void Eat() { Console.WriteLine(Name + "正在吃东西"); } public abstract void Speak(); } class Dog : Animal { public Dog(string name) : base(name) { } public override void Speak() { Console.WriteLine(Name + ":汪汪"); } } class Cat : Animal { public Cat(string name) : base(name) { } public override void Speak() { Console.WriteLine(Name + ":喵喵"); } } Animal a1 = new Dog("旺财"); Animal a2 = new Cat("小花"); a1.Eat(); a1.Speak(); a2.Eat(); a2.Speak(); ``` 理解: 1. `Animal` 是抽象类,不能直接 `new Animal()` 2. `Eat()` 是普通方法,子类可以直接继承使用 3. `Speak()` 是抽象方法,父类只规定“动物要会叫” 4. `Dog` 和 `Cat` 必须用 `override` 实现自己的 `Speak()` 5. `Animal a1 = new Dog()` 可以配合多态使用 ---- # 抽象类不能直接实例化 错误写法: ```csharp abstract class Animal { public abstract void Speak(); } Animal animal = new Animal(); ``` 原因: 抽象类是不完整的类型,里面可能有没实现的抽象成员 所以不能直接创建对象 正确用法: ```csharp Animal animal = new Dog("旺财"); animal.Speak(); ``` ---- # 抽象方法必须在子类中实现 如果子类不是抽象类,就必须实现父类中所有抽象方法。 ```csharp abstract class Shape { public abstract double GetArea(); } class Circle : Shape { public double Radius; public Circle(double radius) { Radius = radius; } public override double GetArea() { return 3.14 * Radius * Radius; } } ``` 错误写法: ```csharp class Circle : Shape { } ``` 原因: `Circle` 是普通类,但没有实现 `Shape` 要求的 `GetArea()` ---- # 子类也可以继续抽象 如果子类暂时不想实现所有抽象方法,那么这个子类也必须声明成 `abstract`。 ```csharp abstract class Shape { public abstract double GetArea(); public abstract double GetLength(); } abstract class RoundShape : Shape { public override double GetLength() { return 0; } // GetArea 暂时不实现,所以 RoundShape 也必须是抽象类 } ``` 理解: 普通类必须把抽象成员补完整 抽象类可以继续把没补完的内容留给下一级子类 ---- # 抽象类里可以有什么 抽象类不只是能写抽象方法,也可以写普通成员。 ```csharp abstract class Character { public string Name; public int Hp; public Character(string name, int hp) { Name = name; Hp = hp; } public void TakeDamage(int damage) { Hp -= damage; Console.WriteLine(Name + "受到了" + damage + "点伤害"); } public virtual void Move() { Console.WriteLine(Name + "正在移动"); } public abstract void Attack(); } ``` 抽象类中可以有: 1. 成员变量 2. 成员属性 3. 构造函数 4. 普通方法 5. 虚方法 6. 抽象方法 7. 抽象属性、抽象索引器、抽象事件 一句话: 抽象类可以有“已经写好的公共功能”,也可以有“要求子类必须补上的功能” ---- # 抽象类可以有构造函数 抽象类不能直接 `new`,但它仍然可以有构造函数。 原因: 创建子类对象时,会先构造父类那一部分 ```csharp abstract class Enemy { public string Name; public Enemy(string name) { Name = name; Console.WriteLine("Enemy构造"); } public abstract void Attack(); } class Boss : Enemy { public Boss(string name) : base(name) { Console.WriteLine("Boss构造"); } public override void Attack() { Console.WriteLine(Name + "发动攻击"); } } Boss boss = new Boss("魔王"); ``` 执行顺序还是: 1. 先执行父类 `Enemy` 构造函数 2. 再执行子类 `Boss` 构造函数 ---- # abstract、virtual、override 的关系 `abstract` 方法: ```csharp public abstract void Attack(); ``` 特点: 1. 没有方法体 2. 子类必须实现 3. 本身就表示允许被重写,所以子类用 `override` `virtual` 方法: ```csharp public virtual void Move() { Console.WriteLine("默认移动"); } ``` 特点: 1. 有方法体 2. 子类可以选择重写,也可以不重写 3. 子类重写时也用 `override` 对比: 1. `abstract`:父类不给默认实现,子类必须改 2. `virtual`:父类给默认实现,子类可以改 3. `override`:子类真正改写父类的抽象方法或虚方法 ---- # 抽象方法和普通方法的区别 普通方法: ```csharp public void Eat() { Console.WriteLine("吃东西"); } ``` 抽象方法: ```csharp public abstract void Speak(); ``` 区别: 1. 普通方法有方法体,抽象方法没有方法体 2. 普通方法可以直接继承使用,抽象方法必须由子类实现 3. 普通方法可以写在普通类或抽象类里,抽象方法只能写在抽象类里 4. 抽象方法天然就是给多态准备的 ---- # 抽象属性 抽象的不一定只有方法,也可以是属性。 ```csharp abstract class Weapon { public abstract int Damage { get; } } class Sword : Weapon { public override int Damage { get { return 10; } } } ``` 简写: ```csharp class Bow : Weapon { public override int Damage => 8; } ``` 理解: 父类规定“武器必须有伤害值” 不同武器自己决定伤害值是多少 ---- # 抽象类和密封类不能同时用 错误写法: ```csharp sealed abstract class Animal { } ``` 原因: 1. `abstract` 表示这个类必须被继承后才能补完整 2. `sealed` 表示这个类不能被继承 3. 两个要求互相矛盾 这个点可以和[[密封类]]一起记。 ---- # 常见错误 错误1:抽象方法写了方法体 ```csharp abstract class Animal { public abstract void Speak() { Console.WriteLine("动物叫"); } } ``` 抽象方法不能有方法体。 ---- 错误2:普通类里写抽象方法 ```csharp class Animal { public abstract void Speak(); } ``` 抽象方法只能写在抽象类中。 ---- 错误3:子类没实现抽象方法 ```csharp abstract class Animal { public abstract void Speak(); } class Dog : Animal { } ``` `Dog` 如果是普通类,就必须 `override Speak()`。 ---- 错误4:抽象方法上再写 `virtual` ```csharp abstract class Animal { public abstract virtual void Speak(); } ``` 抽象方法本身就表示必须被重写,不需要再写 `virtual`。 ---- 错误5:直接 new 抽象类 ```csharp Animal animal = new Animal(); ``` 抽象类不能直接实例化。 ---- # 什么时候用抽象类 适合用抽象类的情况: 1. 多个子类确实属于同一种父类 2. 父类有公共字段、属性、构造函数或普通方法可以复用 3. 父类还需要强制子类实现某些行为 4. 你希望调用方通过父类统一调用子类对象 例如: - `Animal`:动物都能吃,但不同动物叫声不同 - `Shape`:图形都有面积,但不同图形面积算法不同 - `Character`:角色都有血量,但不同角色攻击方式不同 不适合用抽象类的情况: 1. 只是想规定一种能力,例如“能飞”“能攻击” 2. 多个类之间没有明确的父子关系 3. 一个类需要同时拥有多种能力 这些情况后面通常更适合用[[接口]]。 ---- # 和多态的关系 抽象类经常和多态一起使用。 ```csharp abstract class Skill { public abstract void Release(); } class FireSkill : Skill { public override void Release() { Console.WriteLine("释放火球"); } } class IceSkill : Skill { public override void Release() { Console.WriteLine("释放冰锥"); } } void UseSkill(Skill skill) { skill.Release(); } UseSkill(new FireSkill()); UseSkill(new IceSkill()); ``` 理解: 1. `Skill` 规定所有技能都必须能 `Release()` 2. `FireSkill` 和 `IceSkill` 各自实现释放逻辑 3. `UseSkill` 只依赖抽象父类,不依赖具体技能类 这就是: ==抽象类定规则,多态负责运行时执行不同实现== ---- # 注意 1. 抽象类不能直接创建对象 2. 抽象方法没有方法体,结尾用分号 3. 有抽象方法的类必须是抽象类 4. 普通子类继承抽象类后,必须实现所有抽象成员 5. 抽象类可以有普通方法、字段、属性和构造函数 6. 抽象方法不能写 `private`,因为子类需要能重写它 7. 抽象方法不能同时写 `virtual`,因为它本身就是虚的 8. 抽象类不能和 `sealed` 同时使用 ---- # 一句话记忆 ==抽象类不能直接 new,抽象方法只定规则不写实现,子类必须 override。== 再压缩一点: ==父类抽象定规矩,子类重写补细节。== ---- # 面试/复习时怎么说 可以这样答: “抽象类是用 `abstract` 修饰的类,不能直接实例化,主要用来作为父类。 它可以包含普通成员,也可以包含抽象成员。抽象方法没有方法体,只声明方法签名,普通子类继承后必须用 `override` 实现。 抽象类适合用来提取多个子类的共同代码,同时强制子类实现不同的行为,常和继承、多态一起使用。” ---- 引用: 1. Microsoft Learn: [abstract (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/abstract) 2. Microsoft Learn: [Abstract and Sealed Classes and Class Members](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/abstract-and-sealed-classes-and-class-members) 3. Microsoft Learn: [Inheritance - derive types to create more specialized behavior](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/object-oriented/inheritance) #抽象类 #抽象方法 #核心