零基础自学C#——Part4:类的表现形式

上一部分讲解了类(如何创建类,类的实例化等)以及一些基于类上的基本操作,比如继承,多态,封装,重载和重写(覆盖),这一部分,我们将细分化的介绍类的更多表现形式。

一、静态类

就像我们上一部分说的那样,我们在类内使用static声明的变量,称为类的静态成员变量,这样的函数称为类的静态成员函数。

为什么我们要用静态类呢?一般当这个类很频繁被调用,定义成静态的就不用每次给他实例化了,直接用类去调用可以了,像工具类一般都定义成静态的,这样不用每次实例化了,直接用类名去调用(普通类调用时,必须先要给他new 下,才能去调用)

特点:(1)不能被实例化、不能被继承

           (2)不能包含实例构造函数,但是可以包含一个静态构造函数,且静态构造函数不可被调用

1:静态修饰符

const:在类内声明的const常量,在类外访问时,只能通过类名进行访问(不可通过实例化类名)

MyClass myClass= new Myclass();
int a = MyClass.mVaule    而不可为myClass.mVaule

             在类内声明的const常量,只能在声明时进行初始化,不允许任何其他地方对其初始化

public const int mVaule1=10;

              在某种程度,被const修饰的常量,不可变值

readonly:在类内声明的readonly,不可与const共同修饰一个数据类型

public readonly const int mVaule;   //这样的声明方式不允许

                    readonly修饰的类型,可以被类的实例进行访问,但不可修改值。

                    readonly的初始化,只能发生在构造函数或声明中。

2:静态构造函数

     ①静态构造函数,不需要增加访问修饰符

     ②静态构造函数,无论多少实例,都只被调用一次,而且都只是被系统自动调用一次。

public class Class3;
{
  static Class4()
  {
    Debug.Log("Father");
  }  
}

public class Class4: Class3
{
   public Class3()
   {
     Debug.Log("Son");
   }
   
}

 public calss ClassTest:MonoBehaviour
{
  void Start()
  {
    Class3 iClass1 =new Class3();
    Class3 iClass2 =new Class3();
  }
}

如上代码,父类声明了静态构造函数,在输出时,只输出一次Father,而输出两Son。

3:静态类

①:静态类不允许有实例构造函数,只允许存在一个静态构造函数

②:不能被实例化

③:静态类内部成员,必须是静态成员,函数也必须是静态成员函数

public static class Myclass
{
  public const int mVaule=10;   //可以添加静态修饰符
  public static int mVaule;    //成员必须是静态
  public static void Show(){}  //函数必须是静态
}

④:静态类无法被继承

二、抽象类

可以在类前加static,使之成为抽象类,它一般是为子类提供设计思想,配合多态用于代码的架构设计。

特点:

          不能被实例化

           抽象类可以继承抽象类

          支持构造函数,静态构造函数只执行一次;其他构造函数会根据实例不同,分别再次调用

          允许virtual虚函数

          抽象类中不一定有抽象方法,但是有抽象方法的类一定是抽象类,但抽象函数不允许有函数体,且必须显示覆盖父类的该方法。

public abstract class MyClass6   
{
    public MyClass6()
    {
        Debug.Log("MyClass6默认构造函数");
    }
    static MyClass6()
    {
        Debug.Log("MyClass6静态构造函数"); //静态构造函数只执行一次
    }
    public void Show1()                    //普通构造函数会根据实例多次调用
    {
        Debug.Log("普通Show1");
    }

    public virtual void Show2()   //可以使用virtual
    {
        Debug.Log("Myclass6 virtual Show2");
    }
    public abstract void Show3();  //定义抽象方法,不允许有函数体,且在实现时候必须使用覆盖

}
public class MyClass7:MyClass6      //抽象类可以继承
{
    public MyClass7()
    {
        Debug.Log("MyClass7");
    }
    public override void Show2()    //Show2的重写
    {
        Debug.Log("MyClass7 override Show2");
    }
    public override void Show3()    //Show3抽象过了,想实现也必须重写
    {
        
    }
}

public class Abstract : MonoBehaviour
{

    void Start()
    {
        Myclass7 myClass7=new Myclass7();
        Myclass7 myClass8=new Myclass7();
    }

如上的输出结果,则只输出一次静态构造函数

三、密封类(接口)

密封类一般用于防止重写某些类或接口影响功能的稳定,在class前加sealed,变为密封类。

特点:不能被继承,但可以继承别的类或者接口

           抽象abstruct和sealed不能共存

           密封类内的成员函数,不得声明成sealed

public sealed class MyClass8
{

}
   public class MyClass9:MyClass8 //不允许被继承
   {

   }


public class Myclass9
{
  public sealed class Myclass10:Myclass9 //允许继承其他类
  {
   
  }
}

四、泛型类&泛型方法

我们在处理一组功能相同,仅类型不同的任务的时候,可以在类名后添加<T1,T2,T3..>,将变成一个泛型类,泛型T1,T2,T3,可以通过where关键字来限定类型。

特点:

     在声明时可以不指定具体的类型,但是new实例化时必须指定T类型

     可指定泛型类型约束

     如果子类也是泛型的,那么继承的时候可以不指定具体类型

1:泛型类的效率

#需求:在类内定义一个数组,让这个类具备设置数据和访问数据的能力

 ①:不使用泛型类

我们必须分别创建两个类,分别用来存储int类型和string类型的数组

public class MyClass               //创建一个类用来存储int类型的数组
{
  private int[]m_array;   //声明了私有Int类型的数组m_array
  
  public MyClass(int size) //构造函数,设置容量
  {
    m_array=new int[size];
  }
  public void Set(int index,int value) //设置数据的方法,int类型的下标和数据
  {
    m_array[index]=value;  
  }
   public int Get(int index)    //设置了访问数据的方法
   {
     return m_array[index];      //返回Index   
   }


public class MyClass1              //创建一个类用来存储string类型的数组
{
  private string[]m_array;   //声明了私有string类型的数组m_array
  
  public MyClass1(string size) //构造函数,设置容量
  {
    m_array=new string[size];
  }
  public void Set(int index,string value) //设置数据的方法,string类型的下标和数据
  {
    m_array[index]=value;  
  }
   public string Get(int index)    //设置了访问数据的方法
   {
     return m_array[index];      //返回Index   
   }



}

public class Tclass 
void Start()
{
  MyClass myClass= new MyClass(5);  //实例化后设置容量
  myClass.Set(0,1);                 //设置数据
  myClass.Set(1,2);
  int a=myClass.Get(0);
  int a=myClass.Get(1);
  Debug.LogFormat("第(0)号位,值:{1}",0,a);
  Debug.LogFormat("第(1)号位,值:{1}",1,b);
}

②使用泛型

public class MyClass<T>               //创建一个泛型类,表示方法如图
{
  private T[]m_array;   //声明了私有T类型的数组m_array
  
  public MyClass(int size) //构造函数,设置容量
  {
    m_array=new T[size];           //构造函数也使用T类型
  }
  public void Set(int index,T value) //设置数据的方法,T类型的下标和数据
  {
    m_array[index]=value;  
  }
   public T Get(int index)    //设置了访问数据的方法
   {
     return m_array[index];      //返回Index   
   }

public class Tclass
void Start
{                            //在原本T的位置可以改成string,这样就可以存储string类型的数据了
MyClass<string> myClass = new MyClass<string>(5);  
myClass.Set(0,"1");
myClass.Set(1,"2");
string a =myClass.Get(0);
string b =myClass.Get(1);
Debug.LogFormat("第(0)号位,值:{1}",0,a):
Debug.LogFormat("第(1)号位,值:{1}",0,b):
}

2:存储类的数据类型

①:不使用where限定

public calss MyClassType      //创建需要被存储的类
{
  public int a;
  public MyClassType(int value)  //创建类的成员
  {
    this.a=value;
  }
}

public class MyClass<T>               
{
  private T[]m_array;   
  
  public MyClass(int size) 
  {
    m_array=new T[size];        
  }
  public void Set(int index,T value)
  {
    m_array[index]=value;  
  }
  public T Get(int index)  
  {
    return m_array[index];   
  }



void Start()
{
  MyClass<MyClassType>  myClass =new MyClass<MyClassType>(5);  //在实例化的时候也需要改变类型
  myClass.Set(0,new MyClassType(1));
  myClass.Set(1,new MyClassType(2));
  MyClassType a=myClass.Get(0);
  MyClassType b=myClass.Get(1);
  Debug.LogFormate("第(0)号位,值:{1},",0,a,a);
  Debug.LogFormate("第(1)号位,值:{1},",1,b,a);
}

②使用where限定

public calss MyClassType      //创建需要被存储的类
{
  public int a;
  public MyClassType(int value)  //创建类的成员
  {
    this.a=value;
  }
}

public class MyClass<T> where T:MyClassType   //使用where来限定T的类型           
{
  private T[]m_array;   
  
  public MyClass(int size) 
  {
    m_array=new T[size];        
  }
  public void Set(int index,T value)
  {
    m_array[index]=value;  
  }
  public int Get(int index)  //此处直接修改被存储类的类内定义的成员类型
  {
    return m_array[index].a;   
  }



void Start()
{
  MyClass<MyClassType>  myClass =new MyClass<MyClassType>(5);  //在实例化的时候也需要改变类型
  myClass.Set(0,new MyClassType(1));
  myClass.Set(1,new MyClassType(2));
  int a=myClass.Get(0);                      //直接使用int访问即可
  int b=myClass.Get(1);
  Debug.LogFormate("第(0)号位,值:{1},",0,a);
  Debug.LogFormate("第(1)号位,值:{1},",1,b);
}

NET含有以下五种泛型约束:

1:where T:class|T 必须是一个类

2:where T:struct|T 必须是一个结构类型

3:where T:new()|T 必须要有一个无参数的构造函数

4:where T:NameOfBaseClass |T 必须继承名为NameOfBaseClass的类

5:where T:NameOfInterface |T 必须实现名为NameOfInterface的接口

3:泛型方法

public void Show<X>(X A)    //这里的X代表使用泛型的类型,A代表你创建的类型的名称
{
  Debug.Log("A="+A); 
}


void Start()
{
  Son son= new Son();
  son.Show<string>("string");  //在外部调用的时候会自动显示需要的类型,后方括号内可写名称
}

4:泛型在继承中的使用,子类给父类限定方法

public class BaseParent<T,X>      //父类,赋值类型来自子类
{

}
public class Son<T,X>:BaseParent<T,X>   //在定义类的时候不指定类型
{

}

public class Son:BaseParent<int,string>   //在定义类的时候指定父类的类型
{

}
public class Son<T,X,Y,A>:BaseParent<T,X>   //父类延迟赋值的同时,给自己增加了类型Y,A
{

}

五、接口

接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因

此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。

可以使用interface+name,就形成了一个接口。

下面这位博主讲的更为详细。

JAVA基础——接口(全网最详细教程)_刘扬俊的博客-CSDN博客_java接口

特点:

     接口只声明接口函数,不包含实现

     接口函数访问修饰符,必须是public,默认也是public

     接口成员函数的定义时派生类的责任,接口提供了派生类应遵循的标准接口

     接口不可被实例化

     接口可以继承其他接口,接口允许多重继承

1:接口的具体代码实现

public interface BaseInterface1    //接口的引入,interface +name 
{
    void ShowWindow();              //接口可声明函数
    void HideWindow();
    
!!!    void Show1()               //接口不可包含函数体
       {
      
!!!    }
    
!!!     private void Show3();      //不能将函数的声明改为私有的
}

public interface BaseInterface2
{
     void PlaySound();
     void CloseSound();
}
public interface MyInterface:BaseInterface1,BaseInterface2   
                                     //接口允许单一继承,也允许多重继承

{

}
public class MyClass16
{

}
public class Tinterface : MonoBehaviour
{
 
    void Start()
    {
  !!!  BaseInterface inter =new BaseInterface();   //接口不可以被实例化
    }

2:接口和抽象类的区别和共同点

①:区别

     接口不允许声明变量;而抽象类可以

public interface Myinterface
{
  public int a;                     不可!!!
}


public abstract calss MyAbstract
{
   public int b; 
}

     接口不允许有构造函数(普通和静态),而抽象类可以

public interface Myinterface
{
  Myinterface();
  static Myinterface();                  不可!!!
}


public abstract calss MyAbstract
{
  MyAbstract();
  static MyAbstract();
}

     接口不允许函数实现,而抽象类可以

     接口允许多重继承,而抽象类不可以多重继承

     {接口默认的访问修饰符是public,不允许改为private

     抽象类默认的访问修饰符是private,函数前面若是abstract,那访问修饰符也不能是private,但是非abstract声明的函数允许private,protected. }

②:共同点

     两者都不允许实例化

     函数都支持只声明,不实现

     他们的派生类都必须去实现接口或抽象类的方法

public interface MyInterface
{
  void Show1();
}

public abstract MyAbstract
{
  void Show2();
}

public class MyClass:Myinterface 
{
  public void ShowInterface()   //实现interface中的方法
  {}
}
public class MyClass:MyAbstract
{
  public override void ShowAbstract()  //实现abstract中的方法
  {}
}

六、结构体Struct

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,也叫结构。它使得一个单一变量可以存储各种类型的相关数据,我们使用Struct+name结构来声明一个结构体。

特点:

     结构体可以带方法,字段,索引,属性,运算符,方法和事件

     结构体可以定义构造函数,但不能定义析构函数,无参构造函数(自动定义,且不可改变)

     结构体不能继承其他的结构体或类,但可以实现一个或多个接口

     结构体不能作为其他结构或类的基础结构

     结构成员不能指定为abstract、virtual、protected

     结构体不能实例化

1:struct和class的异同

①相同点:

都支持静态构造函数、自定义函数

struct MyStruct
{
  Static MyStruct(){}
  public void Show(){}
}

class MyClass
{
   Static MyClass(){}
   public void Show(){}
}

结构体和类对于const修饰的变量的使用方式是一样的

访问函数时,结构体和类都必须进行初始化才能访问

MyClass myClass = new MyClass();
myClass.Show();

MyStruct myStruct = new MyStruct();
myStruct.Show();

②不同点:

结构体不允许定义无参构造函数,只允许定义有参构造函数,但类都可以

struct MyStruct
{
  public MyStruct(int value)
  {}
  public MyStruct()            !!!不可
  {}
}

结构体不允许定义析构函数,类可以

结构体不允许声明为virtual,但类可以

struct MyStruct
{
  public virtual void Show2(){}     !!!不可
}

结构体不允许声明为abstratct ,但类可以

结构体不允许声明protected,但类可以

结构体声明的全局普通变量(不带修饰符),不能在声明时直接赋值,只能在构造函数里赋值,但类哪里都可以

struct MyStruct
{
  public int a;
  public MyStruct(int value){a=value}
}

class MyClass
{
  public int a=2000;
}

结构体声明的全局readonly变量,只能在构造函数里赋值,而类都可以

struct MyStruct 
public readonly string str; 
public MyStruct(int value)                             //只能在有参构造函数赋值
{
  a=value;
  str="20";
}



class MyClass
public readonly string str="20";                        //直接赋值
或
public read only string str;
public MyClass(int value){str="20"}                    //构造函数赋值

结构体之间不能相互继承,但是类与类可以

结构体访问成员变量,给变量显示赋值可直接访问,而类必须实例化后才可访问

结构体如果不通过new初始化,是不可以直接访问内部变量(const除外)

MyStruct myStruct;
myStruct.a=2000;
int b =myStruct.a;

MyClass myClass = new MyClass();
int a =myClass.a;

结构体的new并不会在堆上分配内存,仅是调用结构体的构造函数初始化而已

类的new会在堆上分配内存,而且也会调用类的构造函数进行初始化

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Laker404

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值