【Unity|C#】基础篇(3)——类(class)/ 接口(interface)
【学习资料】
《C#图解教程》(第4~7章):https://www.cnblogs.com/moonache/p/7687551.html
电子书下载:https://pan.baidu.com/s/1mhOmBG0
【内容】
-
- 所有类的基类
- 类的组成
- 类的实例化、内存分配
- 类的特性:封装、继承、多态
- 接口
- 扩展知识
- 隐藏基类的成员
- struct、class
- 抽象函数(abstract)、虚函数(virtual)
- 抽象类(abstract)、接口(interface)
【笔记】
所有类的基类: object
类由2部分组成
> 数据成员:存储与类或类的实例相关的数据
> 函数成员:执行代码
类的实例化
> 声明一个类的引用,指向空数据null
> new数据部分,并将引用指向数据
【类的特性】
类的三大特性:封装、继承、多态
> 封装
> 访问修饰符
> 继承
> 派生子类
> 构造/析构函数
> 多态
> 静态多态性:函数重载(不同参数)、运算符重载
> 动态多态性:虚函数、抽象函数、隐藏方法
- 封装
- 继承
- this : 当前类对象
- base : 父类对象
- sealed:密封类,不能被继承
-
sealed class Entity { } class Car : Entity // sealed不能被继承,会报错 { }
-
- C#不支持类的多继承,但可以继承多个接口(interface)
-
- 父类初始化:在初始化列表里进行
-
public Tabletop(double l, double w) : base(l, w) { }
- 构造函数
- 在创建类对象时,会执行构造函数:Car car = new Car()
- 构造顺序:先调用父类的构造,再调用子类的
- 析构函数
- 在释放对象时(GC垃圾回收),会执行析构函数
- 析构顺序:先调用子类的析构,再调用父类的
-
class Entity { // 默认构造函数,无参数 public Entity() { Debug.Log("new Entity"); } // 带参构造函数 public Entity(int id) { Debug.Log("new Entity id=" + id); } // 重写析构函数,一个类只能有一个,且无参数 ~Entity() { Debug.Log("delete Entity"); } } class Car : Entity // sealed不能被继承,会报错 { // 不写父类的构造,默认调用默认构造函数Entity() public Car() { Debug.Log("new Car"); } // 在初始化列表: 调用父类的带参构造函数 public Car(int id) : base(id) { Debug.Log("new Car"); } // 重写析构函数,一个类只能有一个,且无参数 ~Car() { Debug.Log("delete Car"); } }
- 多态
-
静态多态性
-
函数重载(不同参数)
-
public Tabletop(double l, double w) : base(l, w) { }
-
-
-
- 运算符重载
-
public static Box operator +(Box b, Box c) { }
-
-
- 动态多态性:子类重写父类方法
- 虚函数
- 关键字:virtual / override
- 子类重写虚函数后:不管在父类中调用,还是在子类中调用,执行的都是子类重写后的函数 (Car.Move)
- 调用父类虚函数的方法:通过 base 关键字调用(base.Move())
- 注:子类也可不重写父类的虚函数
-
class Entity { public void AI() { Move(); } public virtual void Move() // 虚函数声明 virtual { Debug.Log("Entity.Move"); } } class Car : Entity { // 重写方法 public override void Move() // 重写虚函数 override { Debug.Log("Car.Move"); } }
// 测试Test void Start() { Entity entity = new Car(); Car car = (Car)entity; entity.Move(); // 输出:Car.Move car.AI(); // 输出:Car.Move }
- 虚函数
- 动态多态性:子类重写父类方法
-
-
- 抽象函数
- 关键字:abstract / override
- 注:父类只声明抽象函数,没有具体实现
- 注:存在抽象函数的类,必须声明为抽象类,且不能实例化成对象
-
// 含有抽象函数,必须声明为抽象类,且不能实例化对象 abstract class Entity { public abstract void Move(); // 抽象函数 } class Car : Entity { // 子类必须实现父类的抽象函数 public override void Move() { Debug.Log("Car.Move"); } } void Start() { // 报错,抽象类无法实例化 //Entity entity2 = new Entity(); Car entity = new Car(); entity.Move(); // 输出:Car.Move }
- 抽象函数
-
-
-
- 隐藏方法 (不推荐,容易出错)
- 子类重写父类虚函数时,不写 override 关键字,编译器也会显示警告(warning)
- 注:具体执行父类/子类函数,根据调用环境决定(指向对象的引用类型、父类or子类其他函数中进行调用)
- 测试1:不同引用类型调用Move 、通过AI()函数中调用
-
class Entity { public void AI() { Move(); } public virtual void Move() { Debug.Log("Entity.Move"); } } class Car : Entity { // 不写override public void Move() { Debug.Log("Car.Move"); } } void Start() { Entity entity = new Car(); Car car = (Car)entity; entity.Move(); // 输出:Entity.Move entity.AI(); // 输出:Entity.Move car.Move(); // 输出:Car.Move car.AI(); // 输出:Entity.Move }
-
- 测试2:子类重写AI()函数
-
class Entity { public virtual void AI() { Move(); } public virtual void Move() { Debug.Log("Entity.Move"); } } class Car : Entity { public override void AI() { Move(); } // 不写override public void Move() { Debug.Log("Car.Move"); } } void Start() { Entity entity = new Car(); Car car = (Car)entity; entity.Move(); // 输出:Entity.Move entity.AI(); // 输出:Car.Move car.Move(); // 输出:Car.Move car.AI(); // 输出:Car.Move }
-
- 隐藏方法 (不推荐,容易出错)
-
【接口】
> 关键字:interface
> 相当于是个规则,里面只能有:方法、属性、索引、事件
> 一个类只能继承一个父类,但可以实现多个接口
> 注:接口 也可以 继承另一个 接口
> 注:一个类继承了接口后,接口中所有的方法(包括属性、索引、事件)都必须实现
> 注:实现接口中的方法必须定义为 public
interface EntityInterface { int Value { get; set; } // 可以声明属性,但子类中必须实现 void Move(); // 不用写修饰符(public) //int value; // 不能定义变量 //void AI() { Move(); } // 不能实现接口函数 } class Car : EntityInterface { // 必须实现接口中的属性,且必须为 public public int Value { get; set; } // 必须实现接口中的函数,且必须为 public public void Move() { } }
【扩展知识】
- 隐藏基类的成员
- 子类通过new关键字,隐藏父类的成员数据、方法
- 具体调用的是哪个?根据调用环境决定(指向对象的引用类型、父类or子类其他函数中进行调用)
-
- 案例1:不同引用类型获取.value、调用父类的PrintValue
-
class A { public int value = 1; public void PrintValue() { Debug.Log("A.value=" + value); } } class B : A { public new int value = 2; } void Start() { A a = new B(); B b = (B)a; Debug.Log(a.value); // 1 Debug.Log(b.value); // 2 a.PrintValue(); // A.value=1 b.PrintValue(); // A.value=1 }
-
- 案例2:子类隐藏父类的PrintValue
-
class A { public int value = 1; public void PrintValue() { Debug.Log("A.value=" + value); } } class B : A { public new int value = 2; // 隐藏父类函数 public new void PrintValue() { Debug.Log("B.value=" + value); } } void Start() { A a = new B(); B b = (B)a; a.PrintValue(); // A.value=1 b.PrintValue(); // B.value=2 }
-
- 案例3:子类重写父类的PrintValue
-
class A { public int value = 1; public virtual void PrintValue() { Debug.Log("A.value=" + value); } } class B : A { public new int value = 2; // 重写父类函数 public override void PrintValue() { Debug.Log("B.value=" + value); } } void Start() { A a = new B(); B b = (B)a; a.PrintValue(); // B.value=2 b.PrintValue(); // B.value=2 }
-
- 案例1:不同引用类型获取.value、调用父类的PrintValue
-
struct 、class
-
类 是 引用类型,结构体 是 值类型
-
因此结构体不能=null,也不需要使用new
-
结构体 不支持继承
-
结构体 不能声明 默认(无参)构造函数,不能声明 析构函数
-
结构体成员 不能指定为 abstract、virtual 或 protected
-
抽象函数(abstract) 、虚函数(virtual)
-
abstract:父类只声明没有具体实现,子类必须重写实现父类中的抽象函数
-
virtual:子类可以不重写父类的虚函数
-
抽象类(abstract) 、接口(interface)
-
抽象类:依然是一个类,不能被实例化,它仍然包含类的函数
-
接口:相当于是个规则,里面只能有方法、属性、索引、事件
-
抽象类:有抽象的方法,也有不抽象的方法。子类必须实现父类的抽象方法
-
接口:继承了接口后,所有的接口方法都必须实现
-
一个类只能继承一个父类,但是可以实现多个接口