Unity中有关C#的几个要点

37 篇文章 0 订阅

转载https://nickcan.gitee.io/2018/02/25/Unity%E4%B8%AD%E6%9C%89%E5%85%B3C-%E7%9A%84%E5%87%A0%E4%B8%AA%E8%A6%81%E7%82%B9/

大致总结下C#中几个常见知识点的个人理解,以对基础已经有所了解为前提。不定期更新~!

ref/out

  • 区别:ref参数需要在传入前就初始化,out参数在函数结束前需要至少赋值一次。
  • 用处:1.需要返回多个数值的时候 2.需要修改值类型参数 或者 不希望传入值类型参数时发生Copy。
  • 原理:ref参数解析出来,其实传入的是参数的存储地址,类似C++的&地址符。out也一样。从而实现像修改引用类型一样,修改值类型。
    深度思考ref和out及其使用情景
void RefOutTest()
{
	int a;
	// refTest(ref a);//error: a需要先初始化
	a = 1;
	refTest(ref a);
	outTest(out a);
	Debug.Log("out - " + a);// out - 2
}
void RefTest(ref int a)
{
}
void OutTest(out int a)
{
	a = 2;
}

string

  • string 是引用类型哦~!看着像是值类型,实际上是因为每次对string操作,它都会返回一个新的string,所以看起来就跟值类型一样,直接改变了值。
  • 比较惊讶的是!同样的字符串内容,在堆内存中只保留一份。感觉有点像是,一种字符串,便是一个静态类实例。
    net中String是引用类型还是值类型
string str1 = "abc";
string str2 = "abc";
Debug.Log(object.ReferenceEquals(str1, str2)); //true
Debug.Log(object.ReferenceEquals(str1, "abc"));	//true!!

装拆箱

  • 装箱:从值类型到引用类型,从栈到堆。(int转为object)涉及到 1.在堆中开辟空间 2.把值类型的值复制到新开辟的空间中(可以理解成new了一个class,然后把复制到其中一个成员变量里)
  • 拆箱:从引用类型变回值类型,从堆到栈。(object转为int)涉及到 1.从引用获取到堆中的存储位置 2.复制到栈中
  • 因为涉及到申请内存空间,当已分配的内存空间不足时,就会触发GC,引起后续一系列性能消耗,所以要避免频繁的装箱拆箱。

迭代器

  • IEnumrable:IEnumrator GetEnumrator()。
  • IEnumrator: object Current,bool MoveNext(),Reset()。
  • Enumrator实际上是被解析成一个类,类通过简单的状态机(MoveNext)控制实现迭代器。而IEnumrable提供一个获取迭代器的方法,本质上是 new 一个 Enumrator类,并返回。
  • Enumrator状态分为 before,running,suspended,after 四个状态,在MoveNext中通过switch-case来实现状态机。
  • 迭代器是迭代器模式和状态模式的混合。
  • StartCoroutine的参数类型是IEnumrator,而不是IEnumrable。
  • foreach 会自动调用 IEnumrable.GetEnumrator 从而通过迭代器实现遍历。
    匹夫细说C#:庖丁解牛迭代器,那些藏在幕后的秘密
void Start()
{
	var enumerator = EnumerableTest().GetEnumerator();
	Debug.LogError(enumerator.Current);
	Debug.LogError(enumerator.MoveNext());
	Debug.LogError(enumerator.Current);
	//两个输出结果一致
	StartCoroutine(EnumerableTest().GetEnumerator());
	StartCoroutine(EnumeratorTest());
}
IEnumerable EnumerableTest()
{
	Debug.Log(1);
	yield return new WaitForSeconds(.5f);
	Debug.Log(2);
	yield return new WaitForSeconds(.5f);
	Debug.Log(3);
}
IEnumerator EnumeratorTest()
{
	var enumerator = EnumerableTest().GetEnumerator();
	while(enumerator.MoveNext())
	{
		yield return enumerator.Current;
	}
}

GC(Garbage Collector 垃圾收集器)

  • GC大致过程:首先需要挂起所有托管线程,然后扫描还活着的对象,释放空间,最后恢复线程。扫描过程可能会改变数据在堆中的存放位置,腾出新空间,将已使用和空间的空间重新排列分开,于是同时也要改变栈上堆上的指针指向,如果空间还不够,则要申请更多的空间。
  • GC会自动定时执行,也可手动执行。
  • 减少GC,就要减少不必要的堆内存分配了。比如使用对象池复用一些频繁创建的对象。
  • 影响GC速度的是堆中对象的数量,越多则越慢。
  • 基本算法:1.将所有托管内存标记为垃圾 2.遍历内存块,将在使用的内存块标记为有效 3.释放所有无效的内存块 4.整理堆,减少碎片
    C#知识点扫盲-GC

委托和事件

  • 委托本质上是一个类,继承了System.MulticastDelegate。
  • 三个重要的非公有字段: _target (该委托的实例引用,比如委托指向的方法为类a的某个方法则 target=a), _methodPtr(回调函数(方法)的句柄),_invocationList(一个委托数组,通过System.Delegate.Combine连接两个委托就是将后面的委托加到前面委托的 _invocationList中)。
  • 委托实际上是包装了 执行操作的对象 和 对象要执行的方法 的包装器(类)。

  • 事件本质上是对System.Delegate和委托的封装而已,根本上是通过调用System.Deleagte.Combine等方法对delegate操作的一个委托管理器。
    匹夫细说C#:庖丁解牛聊委托,那些编译器藏的和U3D给的

闭包

  • 闭包:一个方法除了能和传递给它的参数交互之外,还可以同上下文进行更大程度的互动。实际上就是匿名函数会把使用到的变量都保存到自己的类中。
  • 所以局部变量不一定就在栈上,也可能在堆上。(类中的值类型成员是和该类一起存放于堆中的)

容器(数据结构)

Array 数组

  • 固定长度
  • 连续存储

ArrayList 数组

  • 类型不安全(存储类型为object),会发生装箱拆箱,也因此可以存放各种不同类型的值。
  • 可变长,插入方便。

List 数组

  • 类型安全的ArryList。

LinkedList 链表

  • 不连续存储。
  • 增删插入都很方便,只需要修改next指针,但是查找就没数组来的方便了,数组直接用下标。

Queue 队列

  • 本质是数组,环形的数组。
  • 通过一个 head 和 tail 变量来指定该数组的头和尾。
  • 长度不够时,也会和数组一样自动扩容。(因为本质就是用数组来存储数据的)

Stack 栈

  • 本质是数组,垂直的数组。
  • 头和尾不用指定了,就是0和count-1了。
  • 长度不够,自动扩容。同上。

HashTable 散列表

  • 类型不安全。
  • 通过特定的哈希函数,对Key加工,得到一个压缩后的哈希值(数字组成,用来当做数组下标)。哈希值是数组下标!?
  • 冲突避免机制:选择合适的哈希函数
  • 冲突解决机制:如果不同的Key得到了一样的哈希值,这时要采取一定的策略(有规则的)保存他们。
  • HashTable采用的双重哈希(二度哈希)来解决冲突,通过更换哈希函数来解决,据说如果发生冲突,则会导致所有哈希被重新计算。

Dictionary 字典

  • 类型安全的HashTable。
  • 采用链接技术(Chaining)来解决冲突,用额外的数据结构来处理冲突。
  • 一个字典,实际上由 int[] buckets 和 Entry[] entries 两个数组组成。
  • buckets保存指定哈希值对应的数据在哈希表中的下标。
  • entries保存数据链表,即Entry实际上是一个链表类型的数据结构。
  • 如果两个数据拥有同一个哈希值,那么他们会以链表的形式,连接在同一个Entry中。

使用

  • 数组:元素数量固定,需要使用下标
  • 链表:当元素需要能够在列表两端添加时,否则使用List
  • 队列和栈:明确的场景 FIFO,LILO
  • 哈希表和字典:需要使用键值对(key-value)来快速添加和查找,并且元素没有特定顺序时。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C# 实现单例模式有多种方式,其一种常见的方式是使用静态变量。下面是一个在 Unity 使用 C# 实现单例的示例代码: ```csharp public class MySingleton : MonoBehaviour { private static MySingleton instance; public static MySingleton Instance { get { if (instance == null) { instance = FindObjectOfType<MySingleton>(); if (instance == null) { GameObject go = new GameObject(); go.name = "MySingleton"; instance = go.AddComponent<MySingleton>(); DontDestroyOnLoad(go); } } return instance; } } private void Awake() { if (instance == null) { instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } } ``` 这个示例,我们在类定义了一个静态变量 instance,用于存储单例对象。在 Instance 属性,我们首先检查 instance 是否为 null,如果是,就通过 FindObjectOfType 方法查找场景是否已经存在 MySingleton 实例,如果存在就直接赋值 instance,否则就创建一个新的 GameObject,并添加一个 MySingleton 组件,再将其赋值给 instance。最后,我们返回 instance。 在 Awake 方法,我们再次检查 instance 是否为 null,如果是,就将当前实例赋值给 instance,并调用 DontDestroyOnLoad 方法,以便在场景切换时不被销毁。如果 instance 不为 null,说明已经存在 MySingleton 实例了,我们就销毁当前实例。 使用时,我们可以通过 MySingleton.Instance 来获取单例对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值