目录
3. 继承
很多类中有相似的数据,比如在一个游戏中,有Boss类,小怪类Enemy,这些类他们有很多相同的属性,也有不同的,这个时候我们可以使用继承来让这两个类继承自同一个类。
3.1 继承
实现继承:
表示一个类型派生于一个基类型,它拥有该基类型的所有成员字段和函数。
在实现继承中,派生类型采用基类型的每个函数的实现代码,除非在派生类型的定义中指定重写某个函数的实现代码。
在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公共功能时,这种类型的继承非常有用。
接口继承:
表示一个类型只继承了函数的签名,没有继承任何实现代码。
在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。
3.2 多重继承
一些语言(如C++)支持所谓的 “多重继承”,即一个类派生自多个类。
使用多重继承的优点是有争议的:
一方面,毫无疑问,可以使用多重继承编写非常复杂、 但很紧凑的代码。
另一方面,使用多重实现继承的代码常常很难理解和调试。
C#不支持多重实现继承。
C#允许类型派生自多个接口— — 多重接口继承。
这说明,C#类可以派生自另一个类和任意多个接口。
更准确地说,System.Object 是一个公共的基类,所以每个 C#(除了Object类之外)都有一个基类,还可以有任意多个基接口。
3.3 实现继承
如果要声明派生自另一个类的一个类,就可以使用下面的语法:
class MyDerivedClass : MyBaseclass
{
// functions and data members here
}
如果类(或 结构)也 派生 自接口,则用逗号分隔列表中的基类和接口:
public class MyDerivedClass: MyBaseClass , IInterface1 , IInterface2
{
// etc.
}
3.5 Example: 继承
基类敌人类( hp speed 方法 ai move )。
派生出来的类:
boss类
type1enemy类
type2enemy类
3.5.1 Enemy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_1
{
class Enemy
{
private float hp;
private float speed;
public float HP
{
get { return hp; }
set { hp = value; }
}
public float Speed
{
get { return speed; }
set { speed = value; }
}
public void AI()
{
Console.WriteLine("Enemy 的 AI 方法");
}
public void Move()
{
Console.WriteLine("Enemy 的 Move 方法");
}
}
}
3.5.2 Boss.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_1
{
class Boss : Enemy // 继承自 Enemy
{
public void Attack()
{
this.AI();
this.Move();
// this.hp = 100; // 父类的私有成员和函数,不能在子类中访问
this.HP = 100;
Console.WriteLine("Boss 的 Attack 方法");
}
}
}
3.5.3 Type1Enemy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_1
{
class Type1Enemy : Enemy
{
}
}
3.5.4 Type2Enemy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_1
{
class Type2Enemy : Enemy
{
}
}
3.5.5 Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 继承
namespace Lesson_3_1
{
class Program
{
static void Main(string[] args)
{
Boss boss = new Boss();
// boss.AI(); // 继承:父类里面所有的数据成员和函数成员都会继承到子类里面
boss.Attack();
Console.WriteLine("----------------------- 分割 1");
Enemy enemy = new Boss(); // 父类Enemy声明的对象enemy,可以使用子类去构造new Boss()
// Boss boss2 = new Enemy(); // 子类声明的对象,不能用父类去构造,因为会缺少子类特有的数据,所以不允许
// enemy虽然是使用父类进行了声明,但实际是指向子类构造的对象,
// 所以本质上是一个子类类型的,我们可以通过强制转换转换为子类类型
Boss boss3 = (Boss)enemy;
boss3.Attack();
Console.WriteLine("----------------------- 分割 2");
Enemy enemy4 = new Enemy();
// 一个对象是什么类型,主要是看它是通过什么类型去构造的
// 这里enemy4使用了父类构造的,只有父类的数据,所以不能强制转换成子类类型
// Boss boss4 = (Boss)enemy4;
Console.ReadKey();
}
}
}
3.6 虚方法
把一个基类函数声明为virtual,就可以在任何派生类中重写该函数:
class MyBaseClass{
public virtual string VirtualMethod(){
return "Method is called in base class";
}
}
在派生类中重写另外一个函数时,要使用override关键字显示声明
class MyDerivedClass:MyBaseClass{
public override string VirtualMethod(){
return "Method is called in derivedclass.";
}
}
3.6.1 Example:虚方法
3.6.1.1 Enemy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_2
{
class Enemy
{
public virtual void Say()
{
Console.WriteLine("Enemy Say");
}
public virtual void Attack()
{
Console.WriteLine("Enemy Attack");
}
}
}
3.6.1.2 Boss.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_2
{
class Boss : Enemy
{
// 使用 override,这里是重写了Say
public override void Say()
{
Console.WriteLine("Boss Say");
}
// 没有使用 override,这里是覆盖了Attack
public void Attack()
{
Console.WriteLine("Boss Attack");
}
}
}
3.6.1.3 Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_2
{
class Program
{
static void Main(string[] args)
{
Enemy enemy = new Boss(); // 声明的父类变量,使用子类构造创建
enemy.Attack(); // 在Boss中没有使用override的重写Attack,这里调用的是:父类Enemy的方法
enemy.Say(); // 在Boss中使用override的重写Say,这里调用的是:子类Boss的方法
Console.ReadKey();
}
}
}
3.7 隐藏方法
如果签名相同的方法在基类和派生类中都进行了声明,但是该方法没有分别声明为virtual和override,派生类就会隐藏基类方法。(要使用new关键字进行声明)
基类
class MyBaseClass{
public int MyMethod(){
}
}
派生类(在派生类中把基类同名的方法隐藏掉了)
class MyDerivedClass :MyBaseClass{
public new void MyMethod() {
}
}
3.7.1 Example: 隐藏
3.7.1.1 Enemy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_3
{
class Enemy
{
// 使用 virtual
public virtual void Attack()
{
Console.WriteLine("Enemy Attack");
}
// 使用 virtual
public virtual void Run()
{
Console.WriteLine("Enemy Run");
}
// 没有使用 virtual
public void Walk()
{
Console.WriteLine("Enemy Walk");
}
// 没有使用 virtual
public void Say()
{
Console.WriteLine("Enemy Say");
}
}
}
3.7.1.2 Boss.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_3
{
class Boss : Enemy
{
// 使用 override
public override void Attack()
{
Console.WriteLine("Boss Attack");
}
// 没有使用 virtual
public void Run()
{
Console.WriteLine("Boss Run");
}
// 使用 new
public new void Walk()
{
Console.WriteLine("Boss Walk");
}
// 没有使用 new
public void Say()
{
Console.WriteLine("Boss Say");
}
}
}
3.7.3 Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 隐藏方法
namespace Lesson_3_3
{
class Program
{
static void Main(string[] args)
{
Enemy enemy = new Boss();
enemy.Attack(); // 调用的是子类的方法,父类使用了virtual,并且子类使用了override,需要两个同时满足,才会是重写,才会调用的是子类的
enemy.Run(); // 调用的是父类的方法
enemy.Walk(); // 调用的是父类的方法
enemy.Say(); // 调用的是父类的方法,可以发现,声明函数时,可以不写new,都是覆盖父类方法
}
}
}
3.8 this和base关键字
this可以访问当前类中定义的字段,属性和方法,有没有this都可以访问,有this可以让IDE-VS编译器给出提示,另外当方法的参数跟字段重名的时候,使用this可以表明访问的是类中的字段。
base可以调用父类中的公有方法和字段,有没有base都可以访问,但是加上base.IED工具会给出提示,把所有可以调用的字段和方法罗列出来方便选择
3.8.1 Example:this和base
3.8.1.1 Enemy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_3
{
class Enemy
{
// 使用 virtual
public virtual void Attack()
{
Console.WriteLine("Enemy Attack");
}
// 使用 virtual
public virtual void Run()
{
Console.WriteLine("Enemy Run");
}
// 没有使用 virtual
public void Walk()
{
Console.WriteLine("Enemy Walk");
}
// 没有使用 virtual
public void Say()
{
Console.WriteLine("Enemy Say");
}
}
}
3.8.1.2 Boss.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_3
{
class Boss : Enemy
{
public float mp;
public void SetMp(float mp)
{
this.mp = mp; // 使用this区分mp,this.mp表示是本类中的mp变量
}
// 使用 override
public override void Attack()
{
base.Attack(); // 使用base访问父类中的方法
this.Walk(); // 使用this访问本类中的方法
Console.WriteLine("Boss Attack");
}
// 没有使用 virtual
public void Run()
{
Console.WriteLine("Boss Run");
}
// 使用 new
public new void Walk()
{
Console.WriteLine("Boss Walk");
}
// 没有使用 new
public void Say()
{
Console.WriteLine("Boss Say");
}
}
}
3.8.1.3 Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 隐藏方法
namespace Lesson_3_3
{
class Program
{
static void Main(string[] args)
{
Enemy enemy = new Boss();
enemy.Attack(); // 调用的是子类的方法,父类使用了virtual,并且子类使用了override,需要两个同时满足,才会是重写,才会调用的是子类的
enemy.Run(); // 调用的是父类的方法
enemy.Walk(); // 调用的是父类的方法
enemy.Say(); // 调用的是父类的方法,可以发现,声明函数时,可以不写new,都是覆盖父类方法
}
}
}
运行结果:
3.9 抽象类
C#允许把类和函数声明为 abstract。
抽象类不能实例化。
抽象类可以包含普通函数和抽象函数。
抽象函数就是只有函数定义没有函数体。
抽象函数本身也是虚拟的Virtual(只有函数定义,没有函数体实现)。
在子类(非抽象)继承抽象类时,子类必须实现所有的抽象方法。
类是一个模板,那么抽象类就是一个不完整的模板,我们不能使用不完整的模板去构造对象。
abstract class Building{
public abstract decimal CalculateHeatingCost();
}
3.9.1 Example:抽象类
3.9.1.1 AbsBird.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_4
{
// 使用 abstract 声明一个抽象类
abstract class AbsBird
{
private int speed = 10;
public int Speed
{
get { return speed; }
set { speed = value > 0 ? value : 0; }
}
public abstract void Fly(); // 抽象函数,不能有函数体,即{ }
}
}
3.9.1.2 Crow.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_4
{
// 继承自 抽象类
class Crow : AbsBird
{
// 子类中,必须将抽象方法实现
public override void Fly()
{
Console.WriteLine("Crow Fly, speed = {0}", this.Speed);
}
}
}
3.9.3 Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 抽象类
namespace Lesson_3_4
{
class Program
{
static void Main(string[] args)
{
// AbsBird bird = new AbsBird(); // 不能实例化抽象类
AbsBird bird = new Crow(); // 可以将子类实例化对象,赋值给抽象类变量
bird.Fly(); // 抽象方法一定是virtual的,Crow类中override重写了Fly,所以这里实际调用的是Crow(子类)中的Fly方法
Console.ReadKey();
}
}
}
运行结果:
3.10 sealed密封类和密封方法
C#允许把类和方法声明为 sealed。
对于类,使用sealed,这表示不能继承该类。
对于方法,使用sealed,表示不能重写该方法。必须是子类重写(override)父类方法时,才能使用sealed进行声明。
sealed class SealedClass
{
}
class Crow : BaseClass
{
// 必须是子类重写父类方法时,才能使用sealed
public sealed override void Fly()
{
}
}
3.11 派生类的构造函数
构造函数(会先调用父类的,然后是子类的)。
1. 在子类中调用父类的默认构造函数(无参)
public class MyDerivedClass{
public MyDerivedClass():base(){
//do something
}
}
在这里 :base()可以直接不写,因为默认会调用父类中的默认构造函数。
2. 调用有参数的构造函数
public class MyDerivedClass{
public MyDerivedClass(string name):base(name){
//do something
}
}
3.11.1 Example: 派生类构造函数
3.11.1.1 Base.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_5
{
class Base
{
public Base()
{
Console.WriteLine("Base no params");
}
public Base(string name)
{
Console.WriteLine("Base name = {0}", name);
}
}
}
3.11.1.2 Derived.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_5
{
class Derived : Base
{
public Derived()
: base() // 调用父类无参构造函数
{
Console.WriteLine("Derived no params");
}
public Derived(int hp)
// : base() // 当我们在子类中没有显示调用父类的构造函数时,会默认自动调用父类的无参构造函数
{
Console.WriteLine("Derived hp = {0}", hp);
}
public Derived(string name)
: base(name) // 调用父类的有参构造函数
{
Console.WriteLine("Derived name = {0}", name);
}
}
}
3.11.1.3 Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 派生类构造函数
namespace Lesson_3_5
{
class Program
{
static void Main(string[] args)
{
Derived dd = new Derived();
Derived dd2 = new Derived(10);
Derived dd3 = new Derived("张三");
Console.ReadKey();
}
}
}
运行结果:
3.12 修饰符
3.12.1 修饰符
修饰符,用来修饰类型或者成员的关键字。
修饰符可以指定方法的可见性。
public 和private修饰字段和方法的时候,表示该字段或者方法能不能通过对象去访问,只有public的才可以通过对象访问,private(私有的)只能在类模板内部访问。
protected 保护的,当没有继承的时候,它的作用和private是一样的,当有继承的时候,protected表示可以被子类访问的字段或者方法。
3.12.2 类的修饰符
public class PubClass { }
class MyClass { }
前者(PubClass)可以在别的项目下访问,后者不行。
比如,假设有两个项目A,B。
在项目A中,有一个public class APubClass,有一个class AMyClass,有一个class AMmClass。
那么,在项目A中,可以自由的使用APubClass、AMyClass、AMmClass类型来创建对象。
在项目B中,首先需要引入项目A;之后,在需要使用的C#文件里面增加:using A;然后,在项目B中,只能使用项目A的APubClass类型来创建实例。其他的两个类型不能在项目B中被使用,因为AMyClass、AMmClass这两个类型,不是是public的。
3.12.3 其他修饰符
static可以修饰字段或者方法。修饰字段的时候,表示这个字段是静态的数据,叫做静态字段或者静态属性。修饰方法的时候,叫做静态方法,或者静态函数。
使用static修饰的成员,只能通过类名访问,不能通过对象访问(静态成员可以看出是共享成员,不属于单个对象,所以不能通过对象访问。需要通过类名访问。)。
当我们构造对象的时候,对象中只包含了普通的字段,不包含静态字段。
静态方法,可以访问静态变量,不能访问非静态变量。
普通方法,可以方法静态变量(不可以使用this.xxx,可以使用类名class.xxx),也可以访问非静态变量。
3.12.4 Example: 静态变量和静态方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 静态变量和静态方法
namespace Lesson_3_6
{
class TestClass
{
public static int y; // 静态变量
private int x;
public static void WriteStatic()
{
Console.WriteLine("static function y = " + y);
// 静态方法,属于类,不属于某个对象,所以没有this,也不能访问非静态变量
// Console.WriteLine("x = " + x); // Console.WriteLine("x = " + this.x);
}
public void WriteA()
{
// 非静态方法,可以访问静态变量,但不能使用this.xxx方式访问静态变量,因为静态变量属于类不属于某个对象
Console.WriteLine("y = " + y);
Console.WriteLine("TestClass.y = " + TestClass.y);
Console.WriteLine("x = " + this.x);
}
}
class Program
{
static void Main(string[] args)
{
TestClass.y = 100; // 通过类名,给静态变量赋值
TestClass.WriteStatic(); // 通过类名,调用静态方法
TestClass tc = new TestClass();
tc.WriteA();
TestClass.y = 200;
tc.WriteA(); // 静态变量发生改变时,对象访问它时候也是最新值。
// 可以静态变量还可以理解为,所有对象共用的属性。比如:人有很多(即每个人是一个对象),但大家共用一个地球。
Console.ReadKey();
}
}
}
运行结果:
3.13 接口
3.13.1 定义和实现接口
定义一个接口在语法上跟定义一个抽象类完全相同,但不允许提供接口中任何成员的实现方式。
一般情况下,接口只能包含方法,属性,索引器和事件的声明。
接口不能有构造函数,也不能有字段,接口也不允许运算符重载。
接口定义中不允许声明成员的修饰符,接口成员都是公有的。
一般,声明一个接口,以大写的I开头,表示是一个接口类。
接口不能被实例化;
继承接口的类,必须实现接口中的所有的声明接口。
定义接口(飞翔功能)
public interface IHandler{
public void Test();
}
实现接口
public class MyClass : IHandler {
}
类,可以继承多个接口:
public class MyClass : IHandler, IHandler2, IHandler3 {
}
3.13.4 派生的接口
接口,可以继承接口。
public interface IHandler {
}
public interface IDerived : IHandler { // 接口继承接口
}
3.13.4 Example:接口
3.13.4.1 IFace.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lesson_3_7
{
// 使用 interface声明接口
interface IFace
{
// int a; // 接口不能定义变量
// void Func() {} // 接口中的方法不能实现
// public void Method(); // 不能有修饰符public
void Method(); // 默认所有都是public
}
interface IFace2 : IFace // 接口继承接口
{
void Method2();
}
interface IFace3 // 接口继承接口
{
void Method3();
}
}
3.13.4.2 Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 接口
namespace Lesson_3_7
{
class TestClass : IFace2, IFace3
{
// 接口中的声明,必须全部实现,并且为public权限
public void Method() { Console.WriteLine("Method ----------"); }
public void Method2() { Console.WriteLine("Method2 ----------"); }
public void Method3() { Console.WriteLine("Method3 ----------"); }
}
class Program
{
static void Main(string[] args)
{
IFace fc = new TestClass();
fc.Method();
Console.WriteLine("----------------------------------------- 分割");
IFace2 fc2 = new TestClass();
fc2.Method();
fc2.Method2();
Console.WriteLine("----------------------------------------- 分割");
IFace3 fc3 = new TestClass();
fc3.Method3();
Console.WriteLine("----------------------------------------- 分割");
TestClass tc = (TestClass)fc3;
tc.Method();
tc.Method2();
tc.Method3();
Console.WriteLine("----------------------------------------- 分割");
Console.ReadKey();
}
}
}
运行结果: