只提供记忆使用

C#基础(选择性)

1,枚举
1枚举的命名规则一般在名字的前面加上E_
2枚举一般申明在namespace下的级别中

image-20250311122749277
3switch语句的贯穿特性合理使用可以将相同枚举类型执行相同的逻辑
4字符串转枚举
image-20250311123423056
2,数组
1new的数组默认值为0
2int[] arr = new int[] {1 , 2 , 3 , 4},int[] arr = {1 , 2 , 3 , 4},数组大小自动分配
3数组的“搬家”
image-20250311124648887
4交错数组
image-20250311130419406

3,函数

1值和引用类型
image-20250311131722498

2string作为一个引用类型非常特殊,它具备了值类型的特征,他变我不变,当他重新赋值的时候,会在堆当中重新分配一个内存空间
3params变长参数
image-20250311160832986
这种方式可以向函数里面传入数量不确定的参数,注意后面必须是数组,可以传入0个参数

C#核心

面向对象编程:

封装

类的基本概念

类的实例化
Person p;
Person p2 = null;
Person p3 = new Person();
其中前两行代码只是在栈上申明了内存空间引用类型,并没有分配实际堆上的内存,而后面利用new关键字分配了堆上的内存

Person A; //在栈上声明了一个Person类A,但是指向的内存为空
Person A = new Person(); //在栈上声明了类A同时在堆上为这个类分配了内存空间
Person B = A; //在栈上声明新的内存来表示类B,但是将类B的在堆上的内存指定成了类A在堆上的内存。A与B发生了连接
Person B = new Person(); //类B在栈上的地址没有发生改变,但是它的内存地址变成了新的堆上的内存,与A的联系切断

类的成员变量

类与结构体的区别其中之一在于结构体不能在结构体里面申明它自己作为成员变量
需要注意的一点,虽然可以声明同类的成员变量,但是不可以对他进行实例化,不然就会产生报错

所有的成员变量就算你不在代码当中进行赋值,它也会在类实例化的时候为他赋值一个初始值

其中对于值类型来说,默认值都是0
其中对于bool是false
引用类型都是空

通过default(int)就可以看到一个数据类型的默认值

image-20250311164942750

访问修饰符:
public
private
protected 只有自己和子类可以进行访问

构造函数

构造函数可以被重载
this代表当前调用函数的对象自己
注意
如果不自己实现无参构造函数而实现了有参构造函数
会失去默认的无参构造
构造函数特殊写法
访问修饰符 构造函数名 (参数列表):this(参数1 , 参数2…)
比如说

public Person(int age , string name) : this()
{
	Console.WriteLine("两个参数构造函数调用");
}

如果使用断点,可以发现它先调用无参构造函数,之后再调用这个构造函数,同理你可以对有参数进行相同做法

析构函数

当引用类型的堆内存被回收的时候,就会调用这个函数,
由于C#存在垃圾回收机制,一般不会直接使用析构函数
析构函数是当垃圾真正被回收的时候才会调用的函数

image-20250311180206190

比如这里p在分配了内存空间之后被赋值为空,这个时候原来被分配的内存与引用类型p断开了联系,变成了“垃圾”

垃圾回收(GC)

的过程是在便利堆上动态分配的所有对象
通过识别它们是否被引用来确定哪些对象是垃圾,哪些对象仍然要重复使用
所谓的垃圾就是没有被任何变量,对象引用的内容
垃圾就需要被回收释放
垃圾回收有很多种算法
引用计数,标记清除,标记整理,赋值集合
注意:
GC只负责堆(heap)内存的垃圾回收
引用类型都是存在堆种的,所以他的分配和释放通过垃圾回收机制来进行管理

栈上的内存是由系统自动管理的
值类型在栈种是自动分配内存的,它们有自己的申明周期,不用对他们进行管理。会自动分配和释放

内存回收机制的大概原理
0代内存,1代内存,2代内存
代的概念:
代是垃圾回收机制使用的一种算法(分代算法)
新分配的对象都会被配置在第零代内存当中
在第一次内存回收过程开始的时候垃圾回收器会认为堆当中全都是垃圾
会进行以下的两步
1,标记对象,从根(静态字段,方法参数)开始检查引用对象,标记后为可达对象,未标记为不可达对象,不可达对象就是垃圾
2,搬迁对象压缩堆(挂起执行托管代码线程释放未标记的对象,搬迁可达对象,修改引用地址

大对象总是被分配在第二代内存,目的是减少性能损耗,提高性能
不会对大对象进行搬迁压缩,85000字节以上的对象为大对象。

新东西都在零代,每次触发垃圾回收都会把0代的可达对象迁移到下一代,并且将其中因为垃圾清理形成的内存间隔进行压缩,之后到了2代的时候,运行速度会下降。

手动触发垃圾回收的方法(一般情况下我们不会去频繁调用,都是在loading界面过场景的时候才会进行调用):

GC.Collect();

成员属性

基本概念:
1,用于保护成员变量
2,为成员属性的获取和赋值添加逻辑处理
3,解决3P的局限性
public-内外访问
private-内部访问
protected-内部和子类访问
属性可以让成员变量在外部
只能获取 不能修改 或者 只能修改 不能获取

private string _name;

public string Name
{
	get
	{
        //可以在返回之前添加一些逻辑规则
		//意味着这个属性可以获取的内容
		return name;
	}
	set
	{
		//可以在设置之前添加一些逻辑规则
		//value关键字用于表示从外部传入的值
		name = value;
	}
}

对属性进行保护处理

set
{
	if(value < 0)
	{
		value = 0;
		Console.WriteLine("Money less than zero");
	}
	money = value;
}

对属性进行加密处理

get
{
	return money - 5;
}
set
{
	money = value + 5;
}

注意:加的访问修饰符低于属性的访问权限,不能让get和set的访问权限都低于属性的访问权限

属性可以进行简写,比如单例模式

public static GameObject Instance{get; private set;}

索引器的概念与属性类似,让对象和数组一样可以通过索引访问其中的元素,注意一下语法
image-20250312122528848

image-20250312122644184
同样的你可以弄一个两个下标的索引器

private int [,] array;

public int this[int i , int j]
{
	get
	{
		return array[i , j];
	}
	set
	{
		array[i , j] = value;
	}
}

静态成员

静态成员会在程序执行开始的就在堆当中分配一块静态内存区域,让静态的方法不需要通过实例化直接就可以通过类名就可以使用

1,静态成员基本概念

2早已出现的静态成员

3自定义静态成员

4为什么可以直接点出来使用

5静态函数当中不可以使用非静态成员

6非静态函数可以使用静态成员

7非静态函数可以使用静态成员

8静态成员对于我们的作用

9常量和静态变量

静态类和静态构造函数

用static修饰的类:
特点:只能包含静态成员 不能被实例化

作用:将常用的静态成员写在静态类当中,方便使用

静态类不能被实例化,更加体现工具性的一面

比如console就是一个静态类

静态构造函数:
1静态类和普通类都可以拥有

2不能使用访问修饰符

3不能有参数

4只会自动调用一次

作用:

在静态构造函数中初始化,静态变量

使用:

1静态类中的静态构造函数

2普通类中的静态构造函数

举例:

static class StaticClass
{
	public static int testInt = 100;
	pubilc static int testInt = 100;
	
	static StaticClass()
	{
		Console.WriteLine("Running the Program");
	}
}

实际上这个静态构造函数会在这个类第一次实例化的时候就被调用,下图的StaticClass就是一个静态类

拓展方法

概念: 为现有的非静态变量类型添加新方法
作用:
提升程序的扩展性就比如说int值类型,我们是无法在编译器当中直接对其进行编辑的,但是我们可以通过拓展方法来给int类型添加新的方法
不再需要继承来添加方法
不再需要在对象当中重写方法

特点

1一定是在静态类当中

2一定是一个静态函数

3第一个参数为拓展目标

4第一个参数使用this修饰

image-20250314235018366

image-20250314235250069
为int类型给了一个拓展方法SpeakValue
image-20250314235335863

泛型

实现了类型参数化,达到代码重用的目的
通过类型参数化来实现同一份代码上操作多种类型
泛型相当于类型占位符
定义类或者方法的时候再具体指定类型

泛型的分类:
泛型类和泛型接口
基本语法
class 类名<泛型占位字母>
interface 接口名<泛型占位字母>

泛型函数
基本语法:函数名<泛型占位字母>(参数列表)
注意:泛型占位字母可以有多个,注意用逗号分开
泛型类的定义:
image-20250315000517228
在主函数当中实例化泛型类、
image-20250315000502961
实现了类型的参数化,之后这个value的类型就是int
声明n个占位符

image-20250315000711127

image-20250315000737235
接口泛型
image-20250315000808410
image-20250315000849360
可以看到继承了int类型的泛型接口的类需要实现的成员变量需要是int

泛型函数

image-20250315001053625
tips:default()可以获取一个类型的默认值
同样的,T这个占位符也可以作为参数传递
image-20250315002524448

泛型函数也可以拥有多个占位符
image-20250315002621214

泛型可以看做类名的一部分,比如下面这样的写法其实是合法的

image-20250315002705559
在泛型函数的某些情况,类型可以省略不写
image-20250315002907565

泛型约束

image-20250312125801764image-20250312125709029

第一点:限制泛型T为struct值类型(比如说int,float)

image-20250312131221429

image-20250312154152756

委托事件

delegate关键字,可以申明一个函数,之后要去实现
委托实际上有一点像类似c++的函数指针,可以看做存储各种各样方法的容器,既包括普通的函数,也可以添加匿名函数表达式,不过由于匿名函数没有函数标识符,所以不可以从委托当中移除,灵活性较低。

image-20250315003512732

delegate用法:访问修饰符 delegate 返回值 委托名(参数列表)
语法:访问修饰符 delegate 返回值 委托名(参数列表)

写在哪里:
一般写在namespace 语句块当中,也可以写在class当中
简单记忆委托语法,就是在函数申明语法之前加上一个delegate关键字
注意:委托负责的申明是不能重名的
image-20250315003925523
image-20250315003950683
用法如此
image-20250315004036289
其中MyFun是一个委托,这相当于委托容器当中装载了Fun这么一个函数
通过Invoke关键字可以使用委托
image-20250315004225739