Unity C#基础
Unity C#基础 记录(后续更新)1.面向对象的三大特性2.值类型和引用类型3.装箱和拆箱4.接口与抽象类的区别5.foreach和for循环区别6.string和stringbuilder和stringBuffer区别7.使用List的优化8.GC9.常用数据结构10.委托和事件11.ref和out关键字12.结构体和类有什么区别
1.面向对象的三大特性
说到面向对象的三大特性,首先我们先来了解一下何为面向对象(OOP)。
面向对象 是将现实问题构建关系,然后抽象成类,给类定义属性和方法后,再将类实例化成实例,通过访问实例的属性和调用方法来进行使用,即 对象 = 属性(特征)+ 方法(行为)。
例如我们想要设计几个角色,角色有男女两种性别,外观和行为有一定差异。那么我们可以想到,一个角色不管什么性别,都得有个人样吧,所以我们先仿照自己的构造,在脑海中构思模板的样子(抽象成基类),然后先赋予模板一些共有的行为和特征(定义类的实例方法):
public class HeroInfo
{
/// <summary>
/// 名字
/// </summary>
private string name;
/// <summary>
/// 图标
/// </summary>
public string icon;
/// <summary>
/// 血量
/// </summary>
public int hp;
/// <summary>
/// 最大血量
/// </summary>
public int maxHp;
/// <summary>
/// 防御
/// </summary>
public int def;
/// <summary>
/// 攻击
/// </summary>
public int atk;
public HeroInfo(string name)
{
this.name = name;
}
public virtual void Attack() { }
}此时我们要求创建 战士和法师,分别要求添加怒气和魔法值设定,以及使用攻击方式不同。
那么我们就要在模板的基础上添加和修改了。通过新建两个类继承自HeroInfo,添加怒气和魔法值设定,并重写Attack方法以达到调用相同的方法出现不同结果(多态性);
设计结束,来回顾一下整个过程。
我们先是把角色的共有特征抽象成 HeroInfo基类,这个基类并不用于实例化,而是用于让 战士类和 法师类去继承它,并实现不同的行为。这样我们就避免把一些共有的行为重复在多个类里定义,如果我们以后想对角色的行为进行变动,也只需要修改 HeroInfo,继承 HeroInfo的子类会自动获得新行为,这是继承带来的好处。
我们把 name 设计为受保护变量(private ),外界无法直接访问这个属性,通过构造传入name。这是 封装 带来的好处。
同时我们在 HeroInfo中预留了 Attack方法并用virtual 关键字修饰,所以在他子类中就能重写这个方法,达到调用相同的方法出现不同结果,这是多态带来的好处。
那么整个过程说完,三大特征也显而易见。即:
封装继承多态
封装
类可以将它的成员私有化,只暴露它认为应当暴露给外界的成员。通过私有化成员,外界不必了解这些成员的实现细节。
关键字:
public:公共的。
private:私有的,故名思义只能在声明的作用域内访问。
protect:受保护的,子类才能访问这个类中protect修饰的成员。
internal:内部的,只能在同一个程序集中访问。可以狭义的理解为同一个命名空间下可以访问。继承
子类可以继承父类,从而简单地自动实现父类所有的成员,并增加自己的成员。
通过方法的重写和隐藏,子类还可以修改父类的行为。继承是面向对象程序设计代码复用能力的一种体现。多态
实际上是继承的一种特殊情况,此时,子类会重写父类的行为。
对于相同的方法,不同类型的子类有着不同的实现。接口和抽象类都是多态的体现。
包含virtual虚方法,abstract抽象方法,interface接口
抽象方法的父类必须是抽象类,子类是抽象类可以不重写,抽象类不可以被实例化。
2.值类型和引用类型
引用类型和值类型.
值类型:包含了所有简单类型(整数、浮点、bool、char)、struct、enum。
引用类型包含了string,object,class,interface,delegate,array
值类型和引用类型最主要的区别就是其内存分配的差异,而其中有两个重要的概念。
栈和托管堆
栈:线程栈,由操作系统管理,存放值类型、引用类型变量(就是引用对象在托管堆上的地址)。栈是基于线程的,也就是说一个线程会包含一个线程栈,线程栈中的值类型在对象作用域结束后会被清理,效率很高。
托管堆:进程初始化后在进程地址空间上划分的内存空间,存储.NET运行过程中的对象,所有的引用类型都分配在托管堆上,托管堆上分配的对象是由GC来管理和释放的。托管堆是基于进程的,当然托管堆内部还有其他更为复杂的结构。
1.单独的值类型变量,如局部值类型变量都是存储在栈上面的;
2.当值类型是自定义class的一个字段、属性时,它随引用类型存储在托管堆上,此时她是引用类型的一部分;
3.引用类型数据存储在托管堆上,引用地址在线程栈上,地址指向数据存放的堆上。即引用类型对象存放的是指向数据存放的托管堆上的引用地址。
4.还有一种情况,结构体(值类型)中定义引用类型字段,结构体是存储在栈上,其引用变量字段只存储内存地址,指向堆中的引用实例。
值类型和引用类型的生命周期
值类型在作用域结束后释放。
引用类型由GC垃圾回收期回收。
3.装箱和拆箱
装箱是将值类型转换为引用类型。
拆箱是将引用类型转换为值类型。
例如:
装箱
int value = 100;
object obj = value;拆箱
int value = 100;
object obj = value;
int num = (int)obj;装过箱的对象才能拆箱
那么为什么需要装箱和拆箱呢,一般情况下为了使代码更加通用。通过装箱和拆箱,能使值类型的任何值与Object 类型的值相互转换。
但同时操作也会对效率有一定影响,因为装箱时,产生的是全新的引用对象,所以就会有时间损耗。
如何避免装箱呢。
一是使用函数重载。
二是使用泛型。
根据情况合理使用装箱拆箱。
4.接口与抽象类的区别
抽象类(abstract)
1.包含抽象方法的类,类必须用关键词abstract定义,抽象类中可以包含普通方法。
2.因为C#是单继承,所以只能继承一个抽象类
3.而非抽象派生类就必须实现抽象方法,通过关键词(override)
4.抽象类不能被实例化
5.其中可以定义 字段(成员变量),属性,非抽象方法实现,索引和事件,抽象方法。
接口
1.不能被实例化
2.接口中可以包含 属性,方法声明,索引器,事件
3.接口可以多继承
4.继承接口的类必须实现接口的方法
5.默认修饰符为public
相同点
1.都不能被实例化
2.自身不提供方法实现代码
3.派生类必须实现未实现的方法
不同点
1.接口中不能定义字段(成员变量)和实现的方法
2.接口支持多继承,抽象类不能
3.接口可以作用于struct和class,但抽象只能作用于class。例如struct能继承接口。
5.foreach和for循环区别
1.foreach的语句比较简洁
2.效率要比for要高(因为for对于数组访问的时候,会对索引值得有效值进行检查)
3.不关心数组的起始索引。
4.foreach 不用显示的进行类型转换
5.当集合元素使用foreach 循环时,每循环完一个元素就会释放他。会造成额外的GC开销
6.foreach在循环集合时,不能对集合进行修改。
6.string和stringbuilder和stringBuffer区别
1.string字符序列不可变,如果对string进行赋值会重新new一个对象进行赋值
所以要避免对string重复赋值和拼接,GC频繁,性能消耗大。
2.stringbuilder是字符串可变对象,可通过自带的StringBuffer.方法来改变并生成想要的字符串。对原实例对象做拼接的实例,不会生成新的实例对象。拼接时只开辟一个内存空间,这是性能优化的点。
3.stringBuffer也是字符串可变对象,和stringbuilder差不多。区别是stringbuilder非线程安全的,性能略好点,stringBuffer是线程安全的。
7.使用List的优化
list=new list()会导致每增加一个内容就增加新内存,导致原内存浪费,GC频繁。需要添加一个固定参数,只开辟一个内存,list = new list(50)
性能优化的点
8.GC
GC垃圾回收处理机制,避免内存溢出,定期回收那些没有有效引用的对象内存。
避免方式:
1.减少new的次数
2.字符拼接使用stringbuilder
3.list new的时候规定大小
4.使用枚举替代字符串变量
9.常用数据结构
1.Stack 栈:先进后出,底层通过泛型数组实现。
2.Queue 队列:先进先出,底层通过泛型数组实现。
3.Array 数组 :声明时需要制定长度,声明后不能更改
4.ArrayList 数组列表: 通过object数组实现,所以会有装箱拆箱操作,比较消耗性能,动态扩容。
5.List 列表:通过泛型数组实现,动态扩容(2倍)。
6.LinkList 链表:通过双向链表实现,所以插入和删除元素的效率较高,但同时查找的效率较低。
7.HashTable 散列表: 无序,通过键值对的方式,类型为Object,所以需要拆箱装箱。
8.Directionary 字典:无序,通过键值对的方式,泛型存储不需要类型转换,查找速度快
总结:
修改较频繁时,插入删除什么的首选LinkList。
要求数据有序且需要遍历的数据,首选List。
要求查找数据快,或需要通过键值对方式查找时首选Directionary 字典
10.委托和事件
委托: 一种特殊的变量(本质是函数指针),可以存储方法的引用
1.通过delegate定义委托类型 权限修饰符 delegate 返回值 委托名 (参数类型 参数);
2.通过委托类型定义委托变量
3.将相同签名的方法存入变量,在需要处再调用委托就行。
事件:特殊的委托。
1.通过event修饰的委托变量
2.只能在声明的类中使用。
3.作用与委托类似
11.ref和out关键字
两个关键字都是用来修饰方法的参数
而使用ref前,必须对参数进行初始化。
而使用out,不需要初始化,但在方法内部必须完成赋值。
他们的作用都是为了将值传递变为引用传递。即方法对参数的改变会同时改变原有变量的值。ref和out关键字会强制将值类型变量,按照引用类型传递
12.结构体和类有什么区别
结构体是通过关键字struct修饰的自定义数据类型,与类型相似,类中能定义的结构体也能定义。但是结构体是值类型,类是引用类型。
1.结构体是值类型,类是引用类型。
值类型在栈上存储,引用类型在托管堆中存储。栈的效率相对比托管堆高,但栈的资源有限,所以结构体不太适合处理逻辑复杂的问题。
结构是值类型所以结构之间的赋值可以创建新的结构对象,而类是引用类型,类之间的赋值只是复制引用。
2.结构没有默认的构造函数,但是可以添加构造函数(结构体中不能定义默认的、不带参数(无参)的构造函数,只能定义带参的构造函数。)
3.结构不支持继承,但能实现接口。
怎么选择使用结构体还是类
1.大多数情况下该类型只是一些数据时,结构是最佳的选择。
2.表现抽象和多级别的对象层次时,类是最好的选择。
3.其中有大量的逻辑的对象,类是最好的选择。
页:
[1]