【学习资料】

  《C#图解教程》(第4~7章):
  电子书下载:https://pan.baidu.com/s/1mhOmBG0

 

【内容】

  • 所有类的基类
  • 类的组成
  • 类的实例化、内存分配
  • 类的特性:封装、继承、多态
  • 接口
  • 扩展知识
  • 隐藏基类的成员
  • struct、class
  • 抽象函数(abstract)、虚函数(virtual)
  • 抽象类(abstract)、接口(interface)

 


【笔记】

  所有类的基类: object 

  类由2部分组成

    > 数据成员:存储与类或类的实例相关的数据
    > 函数成员:执行代码

      

Unity中抽象类 unity抽象类与接口_子类

  类的实例化

    > 声明一个类的引用,指向空数据null

       

Unity中抽象类 unity抽象类与接口_父类_02

    > new数据部分,并将引用指向数据

      

Unity中抽象类 unity抽象类与接口_父类_03

 


【类的特性】

封装、继承、多态

    > 封装

      > 访问修饰符

    > 继承

      > 派生子类

      > 构造/析构函数

> 多态

      > 静态多态性:函数重载(不同参数)、运算符重载

> 动态多态性:虚函数、抽象函数、隐藏方法

 

  • 封装

     

Unity中抽象类 unity抽象类与接口_父类_04

 

  • 继承
  • 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子类其他函数中进行调用)

      

Unity中抽象类 unity抽象类与接口_父类_05

  

Unity中抽象类 unity抽象类与接口_ide_06

  • 案例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
}

 

  • struct 、class
  • 类 是 引用类型,结构体 是 值类型
  • 因此结构体不能=null,也不需要使用new
  • 结构体 不支持继承
  • 结构体 不能声明 默认(无参)构造函数,不能声明 析构函数
  • 结构体成员 不能指定为 abstract、virtual 或 protected


  • 抽象函数(abstract) 、虚函数(virtual)
  • abstract:父类只声明没有具体实现,子类必须重写实现父类中的抽象函数
  • virtual:子类可以不重写父类的虚函数


  • 抽象类(abstract) 、接口(interface)
  • 抽象类:依然是一个类,不能被实例化,它仍然包含类的函数
  • 接口:相当于是个规则,里面只能有方法、属性、索引、事件
  • 抽象类:有抽象的方法,也有不抽象的方法。子类必须实现父类的抽象方法
  • 接口:继承了接口后,所有的接口方法都必须实现
  • 一个类只能继承一个父类,但是可以实现多个接口