访问修饰符
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()
{
方法体代码;
}
}
虚方法、抽象类、接口异同
语法对比
虚方法
- 可以在子类选择性的重写;
- 不重写也可被子类调用;
抽象类
- 抽象类可以从接口继承;
- 抽象类中的实体方法在子类中不可以重写,只可以被引用;
- 抽象类中的抽象方法不可以有方法体,抽象类中的抽象方法在子类中必须重写;
- 抽象类中的虚方法在子类中可以选择性的重写;
接口
- 接口只提供方法规约,不提供方法体;
- 接口中的方法不能用关键字修饰;
- 接口里不能有接口和变量;
- 接口里的方法在子类中必须全部实现;
- 接口可以实现多重继承;
抽象类和接口的相同点和不同点
相同点:
- 都可以被继承
- 都不能直接被实例化
- 都不可以包含方法申明
- 子类必须实现未实现的方法
- 都遵循里式替换原则
不同点:
- 抽象类中可以有构造函数,接口中不能
- 抽象类只能单一继承,接口可以多继承
- 抽象类中可以有成员变量,接口中不能
- 抽象类中可以申明成员方法,虚方法,抽象方法,静态方法;接口只能申明没有实现的抽象方法
- 抽象类方法可以使用访问修饰符;接口中建议不写,默认为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 类名:基类或接口
{
类体
}
访问修饰符 sealed override 方法名称(参数列表)
{
方法体
}
轻松学习C#的密封类
装箱与拆箱
- 装箱:值类型转化为引用类型
- 拆箱:引用类型转换为值类型
- 两种类型只存在继承关系的时候,才可能出现装箱或拆箱操作。
- 装箱和拆箱本质是数据在堆空间与栈空间之间的变更,因此频繁的装箱与拆箱会降低代码效率,应该尽量进行装拆箱操作。
eg:
int a = 10;
object b = a;//装箱操作,值类型转化为引用类型
a = b;
a = (int)b;//拆箱操作,引用类型转换为值类型
对象初始化器
- 在一个类中,我们通常使用构造方法来对属性进行赋值,完成对象的初始化。
- 但是当一个类中属性很多的时候,不可能为各种情况都定义构造方法,这个时候可以使用“对象初始化器”来完成属性的赋值。
- 对象初始化器可以手动对构造函数进行重载,主要就是为了减少构造函数的数量。
eg:
类名 对象名 = new 类名(){属性名 = 值;属性名 = 值};
结构体和类
- 结构体和类的区别:
- 结构体和类最大的区别是在存储空间上的,结构体是值类型储存在栈中,类是引用类型储存在堆中
- 结构体和类在使用上很类似,结构体甚至可以用面向对象的思想来形容一类对象。(构造函数需要赋值所有字段)
- 由于结构体不具备继承的特性,所以它不能够使用protected这个访问修饰符,而类可以。
- 结构体成员变量声明不能指定初始值,而类可以
- 结构体申明有参构造函数后,无参构造函数不会被顶掉,而类会
- 结构体不能申明析构函数,而类可以
- 结构体不能被继承,而类可以
- 结构体需要在构造函数中初始化所有成员变量,而类随意
- 结构体不能被static修饰(不存在静态结构体),而类可以
- 结构体不能在自己的内部申明和自己一样的结构体变量,而类可以
- 结构体的特别之处:可以继承接口,因为接口是行为的抽象
- 如何选择结构体和类:
- 想要用继承和多态的时候,直接使用类,比如玩家、怪物等等
- 对象是轻量化的数据集合时,优先考虑结构体,比如位置、坐标时
- 从值类型和引用类型去考虑,如果经常被赋值传递的对象,并且改变赋值对象时,原对象不想跟着改变,就用结构体,比如坐标、向量、旋转等等。