访问修饰符

public

  • 公开的访问权限。
  • 当前类,子类,实例的对象,都可以访问到。

private

  • 私有的访问权限。
  • 只能在当前类内部进行访问使用。
  • 子类,实例对象,都访问不到。

protected

  • 受保护的访问权限。
  • 只能在当前类的内部,以及该类的子类中访问。
  • 实例对象访问不到。

internal

  • 只能在当前程序集(项目)中访问。
  • 在同一个项目中 internal 和 public 的访问权限是一样的。

protected internal

  • protected + internal 的访问权限。

使用场合和默认修饰符

  • 修饰类:只有public 和 internal能修饰类,类的默认访问修饰符是 internal。
  • 修饰类的成员:五种访问修饰符都可以修饰类中的成员,类中的成员默认访问修饰符是 private。

属性

  • 字段是对象的核心数据,如果直接公开容易被恶意赋值。
  • 通过属性的封装,在set中对字段进行安全性校验。
  • 因此通常使用private修饰,再通过属性对字段进行保护
  • 将属性设置为public,然后通过属性间接访问字段

eg:

private 数据类型 字段名;
public 数据类型 属性名(通常为大写开头的字段名)
{
    get
    {
        return 字段名;
    }
    set
    {
        字段名 = value;    
    }
}

解释:

  • 数据类型:和要保护的字段的数据类型一样;
  • 属性名:和字段名一样,只不过首字母要大写;
  • get:当通过属性名取值的时候,会自动调用 get 中的代码;
  • set:当通过属性名给字段赋值的时候,会自动调用 set 中的代码;
  • value:也是系统关键字,代表赋给属性的值;

里式转换原则

  • 父类容器装子类对象,然后结合is、as使用
  • 子类对象可以直接赋值给父类对象(父类名 父类对象 = new 子类名())
  • 子类对象可以调用父类的对象,但是父类对象只能调用自己的成员(子类对象.父类函数())
  • 如果父类对象中装的是子类对象,可以将这个父类对象强转为子类对象(子类名 子类对象 = (子类名)父类对象)

面向对象其他原则:

https://www.lengyueling.cn/archives/56

依赖倒置原则

接口封闭原则

迪米特原则

单一职责原则

开闭原则

引用类型和值类型

值类型

  • 值类型(value type):byte,short,int,long,float,double,decimal,char,bool , struct
  • 值类型变量声明后,不管是否已经赋值,编译器为其分配内存。

引用类型

  • 引用类型(reference type):数组,委托,interface,object,string,class
  • 当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。
  • 当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。

base常见用法

构造函数

编写子类的构造函数,可以通过:base(父类参数)调用基类构造函数,将子类参数传入父类

eg:

public class A
{
    public A()
    {
        Console.WriteLine("Build A");
    }
}
public class B:A
{
    public B():base()
    {
        Console.WriteLine("Build B");
    }
    static void Main()
    {
        B b = new B();
        Console.ReadLine();
    }
}

虚方法

子类的虚方法使用base.父类虚方法调用基类方法

eg:

public class A
{
    public virtual void Hello()
    {
        Console.WiriteLine("Hello");
    }
}
public class B : A
{
    public override void Hello()
    {               
        base.Hello();
        Console.WiriteLine("World");
    }
}

is和as关键字

  • is:判定某个变量是否是某个类型的对象,是则返回true,否则返回false(bool flag = xx is yy)
  • as:将一个对象转换为指定类型的对象,如果转换成功,返回对应对象,否则返回null (xx as yy)
  • 使用as,可以结合里式替换原则调用as类型的方法

虚方法

  • 在父类中使用 virtual 关键字修饰的方法就是虚方法。
  • 在子类中可以使用 override 关键字对该虚方法进行重写。

eg:

父类:
public virtual 返回值类型 方法名()
{
    方法体代码;
}
子类:
public override 返回值类型 方法名()
{
    方法体代码;
}
  • 子类需要继承父类。
  • 将父类的方法标记为虚方法,也就是在父类方法的返回值前加 virtual 关键字,表示这个方法可以被子类重写。
  • 子类重写父类方法,在子类的方法的返回值前加 override 关键字。
  • 父类中的虚方法,子类可以重写,也可以不重写。
  • 父类中用 virtual 修饰的方法,可以用于实现该方法共有的功能(比如初始化该方法),然后在子类重写该方法时,使用 base .方法名调用父类中的该方法。

抽象方法与抽象类

  • 抽象方法的返回值类型前面加入abstract修饰,且无方法体。
  • 在定义类的关键词class前面加入abstract修饰的类就是抽象类。
  • 子类继承抽象类,使用override关键字重写父类中的所有抽象方法。

eg:

父类:
public abstract void Hello();
子类:
//重写抽象方法
public override void Hello()
{
    方法体代码;
}

注意事项:

  • 如果一个类中有抽象方法,这个类就必须是抽象类
  • 抽象类中不一定要有抽象方法,但是抽象方法必须存在于抽象类中。
  • 抽象类不能被实例化,因为抽象类中没有方法体,如果能实例化抽象类,调用这些无方法体的方法是没有意义的,所以无法实例化。

接口

  • 接口使用 interface 关键字定义,没有 class 关键字,接口名一般使用I开头这种方式进行书写,=I 开头的都是接口。
  • 接口中不能包含字段,但是可以包含属性,接口中定义的方法不能有方法体,全是抽象方法,但又不需要用 abstract 修饰。
  • 接口中的成员不允许添加访问修饰符,默认都是 public。
  • 接口中所有的方法都是抽象方法,所以接口不能被实例化。
  • 一个类可以实现多个接口,被实现的多个接口之间用逗号分隔开。
  • 一个接口可以继承多个接口,接口之间也要用逗号分隔。

eg:

//创建接口
interface IFly
{
    void Fly();
}
//实现接口
class Batmobile:IFly
{
    public void Fly()
    {
        方法体代码;
    }
}

虚方法、抽象类、接口异同

语法对比

image-20220107120725322

虚方法

  • 可以在子类选择性的重写;
  • 不重写也可被子类调用;

抽象类

  • 抽象类可以从接口继承;
  • 抽象类中的实体方法在子类中不可以重写,只可以被引用;
  • 抽象类中的抽象方法不可以有方法体,抽象类中的抽象方法在子类中必须重写;
  • 抽象类中的虚方法在子类中可以选择性的重写;

接口

  • 接口只提供方法规约,不提供方法体;
  • 接口中的方法不能用关键字修饰;
  • 接口里不能有接口和变量;
  • 接口里的方法在子类中必须全部实现;
  • 接口可以实现多重继承;

抽象类和接口的相同点和不同点

相同点:

  • 都可以被继承
  • 都不能直接被实例化
  • 都不可以包含方法申明
  • 子类必须实现未实现的方法
  • 都遵循里式替换原则

不同点:

  • 抽象类中可以有构造函数,接口中不能
  • 抽象类只能单一继承,接口可以多继承
  • 抽象类中可以有成员变量,接口中不能
  • 抽象类中可以申明成员方法,虚方法,抽象方法,静态方法;接口只能申明没有实现的抽象方法
  • 抽象类方法可以使用访问修饰符;接口中建议不写,默认为public

使用场景

虚方法:父类中的个别方法用虚方法实现,然后允许子类在有需要的情况下重写这些虚方法。

抽象类:父类定义一系列的规范,子类去把父类里面定义的这些规范全部实现。

接口:是一种功能的扩展,是在原有的类的继承关系以外的新功能的扩展。

举例:动物是一类对象,我们使用抽象类;飞翔是一种行为,我们使用接口

静态

  • 加入static关键字的变量、方法、属性、类只有一个实体,不能被实例化出多个对象,只能通过类名.变量/方法/属性名来访问
  • 静态属性不能用于封装非静态字段,因为静态的类成员是先于非静态的类成员存在的,在还没有对象之前,静态类成员已经存在了。
  • 静态构造方法的作用 是用来初始化静态成员,一个类之能有一个静态构造方法,该静态方法没有任何访问修饰符和参数。可以定义在静态类中也可以定义在非静态类中。
  • 当类中成员都是静态成员时,可以将该类声明为静态类,静态类中不能存在非静态成员,不能实例化对象。

嵌套类

  • 一个类可以定义在另一个类的内部,相对在外部的类被称为外部类,内部的被称为嵌套类
  • 实例化内部类可以通过外部类名.内部类名的方式访问到内部类

eg:

    /// <summary>
    /// 外部类.
    /// </summary>
    class Person
    {
        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public void Hello()
        {
            Console.WriteLine("Person类.");
        }

        /// <summary>
        /// 嵌套类.
        /// </summary>
        public class Web
        {
            public string webName;
            public string webUrl;

            public void Show()
            {
                Console.WriteLine("{0}:{1}", webName, webUrl);
            }
        }

    }

匿名类

  • 如果某个类的实例对象只会使用到一次,可以使用匿名类的方式创建这个对象。
  • 不需要定义类,我们就可以创建一个匿名类对象。
  • 匿名类之恶能用于储存一组只读属性,不能被重新赋值。

eg:

var name = new { Name = "ABC", Age = 100 };

密封类

  • 被 sealed 关键字修饰过的类被称之为“密封类”。
  • 密封类不可以被继承,也就是不能有子类;
  • 在框架设计的时候使用,提高程序的规范性、安全性

eg:

sealed class AAA
{
}

装箱与拆箱

  • 装箱:值类型转化为引用类型
  • 拆箱:引用类型转换为值类型
  • 两种类型只存在继承关系的时候,才可能出现装箱或拆箱操作。
  • 装箱和拆箱本质是数据在堆空间与栈空间之间的变更,因此频繁的装箱与拆箱会降低代码效率,应该尽量进行装拆箱操作。

eg:

int a = 10;
object b = a;//装箱操作,值类型转化为引用类型
a = b;
a = (int)b;//拆箱操作,引用类型转换为值类型

对象初始化器

  • 在一个类中,我们通常使用构造方法来对属性进行赋值,完成对象的初始化。
  • 但是当一个类中属性很多的时候,不可能为各种情况都定义构造方法,这个时候可以使用“对象初始化器”来完成属性的赋值。
  • 对象初始化器可以手动对构造函数进行重载,主要就是为了减少构造函数的数量。

eg:

类名 对象名 = new 类名(){属性名 = 值;属性名 = 值};

结构体和类

  • 结构体和类的区别:
    • 结构体和类最大的区别是在存储空间上的,结构体是值类型储存在栈中,类是引用类型储存在堆中
    • 结构体和类在使用上很类似,结构体甚至可以用面向对象的思想来形容一类对象。
    • 由于结构体不具备继承的特性,所以它不能够使用protected这个访问修饰符,而类可以。
    • 结构体成员变量声明不能指定初始值,而类可以
    • 结构体申明有参构造函数后,无参构造函数不会被顶掉,而类会
    • 结构体不能申明析构函数,而类可以
    • 结构体不能被继承,而类可以
    • 结构体需要在构造函数中初始化所有成员变量,而类随意
    • 结构体不能被static修饰(不存在静态结构体),而类可以
    • 结构体不能在自己的内部申明和自己一样的结构体变量,而类可以
  • 结构体的特别之处:可以继承接口,因为接口是行为的抽象
  • 如何选择结构体和类:
    • 想要用继承和多态的时候,直接使用类,比如玩家、怪物等等
    • 对象是数据集合时,优先考虑结构体,比如位置、坐标时
    • 从值类型和引用类型的去呗上去考虑,如果经常被赋值传递的对象,并且改变赋值对象时,原对象不想跟着改变,就用结构体,比如坐标、向量、旋转等等。