记点乱七八糟的东西,免得自己忘了查起来麻烦
负数二进制
~0 = ~00000000 = (取反)11111111 = (-1)11111110 = (反码)10000001 = -1
负数即,二进制的第一位数代表符号位,0为正数,1为负数。
负数的反码,即符号位不动,其余取反。-5 = 10000101 = (反码)11111010
负数的补码,即反码+1。-5 = (反码)11111010 = (补码)11111011
系统中负数的二进制存的是补码,如:
int x = -17;
string str= Convert.ToString(x,2);
Debug.Log(str);
输出结果:
11111111111111111111111111101111
IEnumerable和IEnumerator
c#中,当一个类继承IEnumerable接口,或者类中实现IEnumerator GetEnumerator()方法,这个类即可使用foreach去遍历。
详情请见:https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.ienumerator?view=netframework-4.7.2
using System.Collections;
using UnityEngine;
public class Test : MonoBehaviour{
void Start(){
TextEach te = new TextEach(new string[] {"111", "222", "333", "444", "555", "666" });
foreach(var t in te) {
Debug.Log("t "+t);
}
}
}
public class TextEach : IEnumerable {
string[] array;
public TextEach(string[] arr) {
array = arr;
}
public IEnumerator GetEnumerator() {
return new TextEachEnum(array);
}
}
public class TextEachEnum : IEnumerator {
public string[] array;
int index = -1;
public TextEachEnum(string[] arr) {
array = arr;
}
public object Current {
get {
return array[index];
}
}
public bool MoveNext() {
index++;
return (index < array.Length);
}
public void Reset() {
index = -1;
}
}
异或 ^
1.0^0=0,0^1=1 0异或任何数=任何数
2.1^0=1,1^1=0 1异或任何数-任何数取反
3.任何数异或自己=把自己置0。 a^a = 0,a^b^a = b^0 = b
4.使某些特定的位翻转,例如对数10100001的第2位和第3位翻转,则可以将该数与00000110进行按位异或运算
10100001^00000110 = 10100111
5.两个变量的值交互
例如交换两个整数a=10100001,b=00000110的值。
a = a^b; //a=10100111 (c = a^b)
b = b^a; //b=10100001 (b = b^c = b^a^b = a)
a = a^b; //a=00000110 (a = c^a = a^b^a = b)
6.a = b^c^d => b = a^c^d
值类型和引用类型
值类型:
值类型是在栈中分配内存,在声明时初始化才能使用,不能为null。
值类型超出作用范围系统自动释放内存。
主要由两类组成:结构,枚举(enum),结构分为以下几类:
1、整型(Sbyte、Byte、Char、Short、Ushort、Int、Uint、Long、Ulong)
2、浮点型(Float、Double)
3、decimal
4、bool
5、用户定义的结构(struct)
引用类型:
引用类型在堆中分配内存,初始化时默认为null。
引用类型是通过垃圾回收机制进行回收。
包括类、接口、委托、数组以及内置引用类型object与string。
示例
值类型保存在栈中,故等值不等址;引用类型保存在堆中,故等值等址。
struct structA = new struct();
structA.value = 10;
struct structB = structA;
structB.value = 20;
//此时structA.value = 10; structB.value = 20;
class classA = new class();
classA.value = 10;
class classB = classA;
classB.value = 20;
//此时classA.value = 20; classB.value = 20;
String
string是一种特殊的引用类型,首先它可以像值类型一样去赋值
string str1 = "aaaa";
其次string的值是不可改变的。修改其中一个字符串,就会创建一个全新的string对象,而原有的值不会发生任何变化。
string str1 = "aaaa";
string str2 = str1;
//str2 = str1 = "aaaa", 且地址相同
str1 = "bbbb";
//会生成一个新的string "bbbb" 此时str1 = "bbbb", 而str2 = "aaaa"
装箱拆箱
概念:
由于C#中所有的数据类型都是由基类System.Object继承而来的,所以值类型和引用类型的值可以通过显式(或隐式)操作相互转换,而这转换过程也就是装箱(boxing)和拆箱(unboxing)过程。而且这个过程是要额外耗费cpu和内存资源的,所以我们要尽量通过重载函数或者泛型GenericList<T>来避免。
装箱:
是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换。对值类型装箱会在堆中分配一个对象实例,并将该值复制到新的对象中。
例如有方法为public SetValue(Object obj);调用的时候传入参数为值类型,SetValue(1);这个时候就会进行装箱操作。
拆箱:
是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。
例如 int x = (int)obj;
接口和抽象类的区别:
- 抽象类可以有构造方法,接口中不能有构造方法;
- 一个类可以实现多个接口,但只能继承一个抽象类;
- 抽象类可以包含静态方法,接口中不能包含静态方法
- 接口不能包含字段,抽象类可以有字段。
- 实现接口的时候必须要实现接口中的所有的方法,不能遗漏任何一个。
- 子类必须override抽象类中的所有抽象属性和抽象方法,如果没有全部override,那么子类必须是抽象类
- 接口可以用于支持回调,而继承并不具备这个特点
/*
*抽象类可以但不是必须有抽象属性和抽象方法,但是一旦有了抽象方法,就一定要把这个类声明为抽象类
* */
public abstract class AbstractClass {
//私有字段
private int m_x;
//公有属性
public int x { get { return m_x; } }
//公有字段
public int y;
//抽象属性(必须是公有的)
public abstract int sum { get; }
//构造方法
public AbstractClass() {
m_x = 10;
y = 5;
}
//抽象方法(必须是公有的)
public abstract int Add();
}
/*
* 接口的成员包括方法、属性、索引器、事件
* 接口中不能包含常量、字段(域)、构造函数、析构函数、静态成员
* 接口中的所有成员默认为public,因此接口中不能有private修饰符
* */
public delegate void Callback();
public interface Interface {
//属性
string s { set; get; }
//方法
void LogS();
event Callback cb;
}
public class Test : AbstractClass, Interface {
//抽象类的实现
public override int sum {
get {
return Add();
}
}
public override int Add() {
return x + y;
}
//接口的实现
public string s {
get {
return s;
}
set {
s = value;
}
}
public void LogS() {
}
public event Callback cb;
}
ref/out
//ref前必须先赋值
int a = 0;
RefMethod(ref a);
int b;
OutMethod(out b);
public void RefMethod(ref int a) {
}
public void OutMethod(out int b) {
//方法内必须赋值
b = 0;
}
Material与ShareMaterial
Renderer.material
当我们引用修改这个属性的时候,Unity会返回该Render下第一个实例化后的material赋予当前的Rederer组件。即每一次引用就会生成一个新的material到内存中。但是在引用后并不会改变我们项目工程中材质球的原始属性设置。这在销毁物体的时候需要我们手动去销毁该material,否则会一直存在内存中。也可以在场景替换的时候使用Resources.UnloadUnusedAssets去统一释放内存。
即,假设Material M1 为白色,场景中的物体A和B,都使用了M1,此时修改A.material 为黑色的时候,A会变为黑色,B还是白色。
Renderer.sharedMaterial
当我们改变Renderer.sharedMaterial的时候,所有使用这个材质球物体都会被改变,并且改变后的设置将会被保存在项目工程中。并不会生成新的material。
即,假设Material M1 为白色,场景中的物体A和B,都使用了M1,此时修改A.sharedMaterial为黑色的时候,A和B都会变为黑色
DrawCall优化
动态批处理
unity自己执行,每一帧把可以进行批处理的模型网格进行合并,再把合并后模型数据传递给GPU,然后使用同一个材质对其渲染。
顶点数太多的物体,无法进行动态批处理,无法减少DC(可以使用静态批处理来减少DC)
不同材质的物体,无法进行动态批处理,无法减少DC
静态批处理
勾选Static(Batching Static),只在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网格中,这意味着这些模型数据不可以在运行时刻被移动。
不同材质的物体,无法通过静态批处理减少DC。但是静态批处理可以通过合并网格来提高性能。
具体如:
- 减少shader中的pass
- shader中少使用顶点属性,或者模型顶点数要尽可能少
- 使用相同的材质
- 合并网格
时间复杂度
常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)
git仓库 .gitignore规则不生效
.gitignore只能忽略原来没有被追踪的文件(即未提交到仓库的),如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。
解决方法就是,我们要先把本地缓存删除(改变成未追踪状态),然后重新提交
git rm -r --cached .
git add .
git commit -m 'update .gitignore'
像数组一样,使用下标访问一个类的对象
public class Data
{
}
public class DataManager
{
List<Data> dataList = new List<Data>();
public Data this[int index]
{
get { return dataList[index]; }
}
}
//使用
DataManager manager = new DataManager();
Data data = manager[0];