简单数据结构
ArrayList
概念
ArrayList是一个C#为我们封装好的一个类,它的本质是一个object数组。
增
Add:增加一个object类型。
AddRange:增加一个ArrayList。
删
Remove:根据内容删除
RemoveAt:根据索引值删除。
Clear:清空。
查
查可以通过索引器实现。
Contains:根据值查找。
改
改可以通过索引器实现。
遍历
foreach遍历。
for遍历
while遍历。
Stack
Stack是一个C#为我们封装好的类,本质是一个object数组,只是封装了特殊的存储规则。
栈是一种先进后出的数据结构。
压栈
push:将数据压入栈中。
弹栈
pop:将数据弹出栈中。
查
peek:查看栈顶元素但不弹栈。
Contains:根据值查看是否有该元素。
改
栈无法改变其中的元素,实在要改,只要清空。Clear
遍历
foreach遍历
while遍历
遍历出来的顺序都是从栈顶到栈底。
作用
取反
Queue
概念
Queue是一个C#为我们封装好的一个类,它的本质是Object数组,只是封装了特殊的存储规则。
队列是一种先进先出的数据结构。
增
Enqueue:增加一个队列元素
取
Dequeue:取出一个队列元素
查
peek:和栈同理
Contains:根据值查看是否有该元素。
改
栈无法改变其中的元素,实在要改,只要清空。Clear
遍历
foreach遍历
将队列转换成object数组进行遍历
while循环出列
作用
可以用来做拾取物品缓存区
Hashtable
可以认为是字典的原型
概念
Hashtable是一个基于键的哈希代码组织起来的键值对,它的主要作用是提高数据查询的效率,使用键来访问集合中的元素。
增
Add:增加一条键值对。
删
Remove:删除一对键值对。
Clear:清空。
查
Containkey:是否存在某个key。
ContainValue:是否存在某个值。
改
只能修改值,不能修改键。
遍历
foreach遍历
作用
可以用来作为类管理器
泛型约束
约束组合使用:
public class LinkNode<T> where T : class,new()
{
public T Value;
public LinkNode<T> NextNode;
}
}
多个泛型约束:
public class LinkNode<T,Q> where T : class,new() where Q : struct
{
public T Value;
public LinkNode<T,Q> NextNode;
}
泛型数据结构
线性表
数据结构简介:
数据结构是计算机存储,组织数据的方式。
数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。
简单来说:就是人定义的存储数据和表示数据之间关系的规则而已。
常见的数据结构:
数组,栈,队列,链表,树,图,堆,哈希表
线性表概念:
线性表是一种数据结构,是由n个具有相同特性的数据元素的有限序列。
例子:
数组,ArrayList,Stack,Queue,链表等。
顺序存储和链式存储是数据结构的两大物理存储类型。
LinkedList
LinkedList是一个C#为我们封装好的类,本质是一个泛型双向列表。
声明
此链表对象需要掌握两个类:LinkedList和他的节点类LinkedListNode。
增
AddLast:在尾节点添加。
AddFirst:在首节点添加。
AddAfter:在一个点的后面添加,需要配合Find来使用
AddBefore:在一个点的前面添加,需要配合Find来使用。
删
Remove:根据值来删除。
RemoveLast:移除尾节点
RemoveFirst:移除头节点。
Clear:清空。
查
Find:根据值返回节点。
改
直接改Value。
遍历
foreach遍历。
while遍历。
委托
委托不受静态关键字的约束,只要函数签名相同就可以调用。
委托常声明在namespace中或者Class语句块中,更多的写在namespace中,这样可以方便同一命名空间下的对象可以直接调用。
委托常作为类的成员变量或者函数参数。
委托赋值使用 = 号
委托增减使用 ± 号
委托链清空使用 = null
事件
事件只能作为成员变量存在于类,接口,结构体中。
事件无法在类的外部赋值和调用,这使事件使用分为了事件源类和响应者类。
事件源类声明事件调用事件
响应者类增减事件
赋值指的是不能在类的外部使用“=”,即不可在类外不清空委托链。
匿名函数
概念
匿名函数就是没有名字的函数
匿名函数的使用主要是配合委托和事件来使用,脱离了委托和事件不会使用匿名函数。
基本语法
delegate (参数列表)
{
//函数逻辑
};
何时使用
- 当函数中传递委托参数时
- 委托或事件赋值时
有返回值的匿名函数和lambda表达式,其返回值类型与委托/事件的要求返回值一致,不用自己声明。
缺点
添加到委托事件容器中后,如果不记录就直接添加会导致无法单独移除。lambda一样。
Lambda表达式
概念
可以将lambda表达式理解为匿名函数的简写,出了写法不同之外,使用上可匿名函数一样。
Lambda语法
(参数列表) =>
{
//函数体,Lambda表达式的参数类型和返回类型都会根据委托事件的要求自动补全。
};
闭包
概念
内层函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。
注意:该变量提供的值并非变量创建时的值,而是父函数的终止值。
协变逆变
协变逆变是用来修饰泛型的。目前只对泛型接口和泛型委托有效。
协变:可以实现子 to 父的安全转换,IFoo<父类> = IFoo<子类>;
逆变:可以实现父 to 子的安全转换,IBar<子类> = IBar<父类>;
要注意:逆变本身没有违反里氏转换原则,下图将对协变逆变进行数值变换分析:
图来源:https://www.cnblogs.com/CLR010/p/3274310.html
关键字
协变:out
逆变:in
适用性
只适用于泛型接口和泛型委托。
作用
用out修饰的泛型参数只能能作为返回值。
用out修饰的父类泛型委托可以装载子类泛型委托,
用in修饰的泛型参数只能作为参数。
用in修饰的子类泛型委托可以装载父类泛型委托,
总结
1、将可变成员作为方法的输入参数,则当前成员的泛型参数可变性必须与输入成员的泛型参数可变性相反。
2、将可变成员作为方法的返回参数,则当前成员的泛型参数可变性必须与输出成员的泛型参数可变性相同。
协变逆变分析图(学会画分析图,更好的分析使用协变还是逆变)
下图仅为示例:
意义
当一个泛型变量即使返回值又是参数类型时,当涉及有父子类转换的时候如果不加协变逆变,其返回值和参数传递时总有一边是不安全的。所以我们需要使用协变逆变来保证安全的引用传递。
多线程基础
概念引导
了解多线程就需要先了解什么是线程,要了解线程就需要知道什么是进程。
进程:
进程是计算机中的程序关于某个数据集合上的一次运行活动,进程是程序的动态体现,是操作系统进行资源分配和调度的基本单位,是操作系统的基础。进程之间可以相互独立的运行,也可以相互访问。
线程:
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程的实际运作单位,一个进程可以包含多个线程。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程。
语法相关:
线程类:Thread
需要引用的命名空间 using System . Threading
新的线程声明:
using System.Threading;
namespace 多线程
{
internal class Program
{
public static void Main(string[] args)
{
//在主线程中声明SubThread1线程
Thread subThread1 = new Thread(SubMain1);
//在主线程中启动SubThread1线程
subThread1.Start();
}
public static void SubMain1()
{
Console.WriteLine("这是一个子线程");
}
}
}
目前这还是一个前台子线程,其生命周期独立于主线程,当主线程结束后依然可以运行。
如果想让这个子线程的生命周期附属于主线程那么就需要将其设置为后台线程。
subThread1.IsBackground = true;
关闭释放一个线程:
如果开启的线程中不是死循环,是能够结束的逻辑那么不需要刻意去关闭它;
如果是死循环想要终止这个线程有两种方式:
- while中的bool标识
- 通过线程提供的方法:subThread1.Abort();(此方法在core版本中会报错)。
线程休眠:
Thread.Sleep(1000);
数值以毫秒为单位,在哪个线程执行就休眠哪个进程。
多线程之间的资源占用问题:
多个线程使用的内存是共享的,都属于该进程,当多个线程要访问同一片内存空间时可以通过加锁的方式来避免问题。(C#的锁可以自动释放)。
注意:锁的参数列表必须为引用类型的参数。
lock (new Mutex())
{
}
意义
unity中多线程可以帮助我们专门处理一些复杂耗时的逻辑运算比如:寻路,网络通信等。
预处理指令
概念
预处理指令指导编译器在实际编译开始之前对信息进行预处理。
预处理指令以#开头
预处理指令不是语句,所以他们不易分号结尾。
常见的预处理指令
#define
定义一个符号,通常与if指令配合使用。
#undef
取消一个定义好的符号。
#if #elif #else #endif
和if语句的规则一样,和#define配合使用常用于告诉编译器进行编译代码的流量控制。
下图为例子:
#warning
#error
告诉编译器是报警告还是错误,一般配合if使用
下图为例子:
反射
反射中将要涉及到的概念:程序集,元数据,反射。
概念:
程序集
是经由编译器编译得到的,在Windows系统中表现为dll库文件和exe可执行文件。
元数据
是用来描述数据的数据。程序中的类,类中的成员等信息就是程序的元数据。,他们被保存在程序集中。
反射
程序运行时,查看其他程序或自身程序元数据的行为就是反射。通过反射我们可以在程序运行的时候动态的实例化对象。
Type
Type是类的信息类,是反射功能的基础。Type是访问元数据的主要方式。
同一类型的type指向同一内存地址,换句话讲,type是唯一的。
语法
(Type的语法在《Day20_面向对象》中)
得到类的程序集信息
Type type = Typeof(string);
type.Assembly;
得到类中的所有公共成员信息
Type type = typeof(string);
MemberInfo[] memberInfo = type.GetMembers();
得到类中的构造函数信息
当试图获得方法(构造函数也是方法)信息时要注意,Get系方法将会要求填写参数列表object[ ]。没有参数就填写null。
得到无参构造函数
Type type = typeof(string);
ConstructorInfo constructorInfo = type.GetConstructor(new Type[0]);//也可以使用null
得到有参构造函数
Type type = typeof(Test);
ConstructorInfo constructorInfo = type.GetConstructor(new Type[]{typeof(int),typeof(string)});//这里填写想要调用的构造函数的参数列表
构造函数调用
string str = constructorInfo.Invoke(null) as string;
//Invoke方法要求传入一个object的参数数组,对于无参构造函数我们传null即可
得到类中的公共方法信息
整体与构造函数相似,但使用的信息类为MethodInfo。
Type type = typeof(Test);
//实例化一个对象
Test test1 = Activator.CreateInstance(type);
//获得方法第一个参数为方法名字,第二个参数为参数列表,参数列表的存在可以使我们调用方法的重载
MethodInfo methodInfo = type.GetMethod("Func",new Type[0]);
//实例方法调用时需要传入类的实例对象,静态方法第一个参数为空即可
methodInfo.Invoke(test1,null);
其他Get方法
得枚举:
GetEnumName
GetEnumNames
得事件:
GetEvent
GetEvents
得接口:
GetInterface
GetInterfaces
得属性:
GetProperty
GetPropertyss
Activator
Activator提供快速实例化对象的类,用于将type对象快速实例化为对象。
调用无参构造
Activator.CreateInstance(type) as Test;
调用有参构造
Activator.CreateInstance(type,1,2,3) as Test;
//与直接调用有参构造函数不同的是,这里的参数列表为参数数组params,我们可以直接传入object的子类
Assembly
Assembly程序集类
主要用于动态加载其他程序集,加载后才能使用Type来使用其他程序集中的信息。
如果想要使用的不是自己程序集中的内容,需要先加载程序集。
三种加载程序集的方法
//一般用于加载在同一文件下的其他程序集
Assembly assembly = Assembly.Load("程序集名称");
//一般用于加载不在同一文件夹下的其他程序集
Assembly assembly = Assembly.LoadFrom("包含程序集清单的文件名称或路径");
//一般用于加载不在同一文件夹下的其他程序集
Assembly assembly = Assembly.LoadFile("要加载文件的完全限定路径");
利用反射访问自定义类库 .dll
特性
概念
特性是一种允许我们向程序集中添加元数据的语言结构。即添加对于类,类的成员,结构体等的额外说明。
特性的本质是一个类,在特性与程序实体关联后,即可在运行时使用反射查询特性信息。
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中,它可以放置在几乎所有声明中,当然,这种放置可以限制。
基本语法
所有的特性类东营当继承Attributes
[特性名(参数列表)]
本质是在调用特性类的构造函数。
可以通过反射来获得特性,使用GetCustomAttributes方法,特性的本质是一个类
限制自定义特性的使用范围
使用官方特性[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
其中
AttributeTargets —— 特性可以用在什么声明上。
AllowMultiple —— 是否允许多个特性实例用在同一个声明上。
Inherited —— 特性是否可以被继承和重写的成员继承。
系统自带特性——过时特性
过时特性:Obsolete
用于提醒用户当期方法等成员已经过时,建议使用方法,这是一个一般加在函数声明上的特性。
[Obsolete("此方法已过时,推荐使用XXX方法", false)]
//第一个参数是使用此方法时的提示信息
//第二个参数true表示使用该方法报错,false表示使用该方法警告
系统自带特性——调用者信息特性
此特性是一系列特性的集合,通常用于函数的可选参数,如果参数不是可选的会报错。
此方法的调用文件路径是什么
[CallerFilePath]
此方法在文件调用行数是多少
[CallerLineNumber]
此方法在那个函数调用
[CallerMemberName]
系统自带特性——条件编译特性
特性名称:
Conditional
通常会与预处理命令#define一同使用,主要用于一些调试代码上。
[Conditional("func")]
//Conditional字符串中为当#define Func后此函数才会执行。
系统自带特性——外部DLL包特性
特性名称:
DLLImport
用于标记非.NET函数,表明该函数在一个外部dll中定义,一般用于调用C或者C++的dll包写好的方法。Unity中非常常见
[DLLImport("Test.dll")]
public static extern int Add(int a,int b);
C#特殊语法
var隐式类型
var是一种特殊的变量类型,它可以用来表示任何类型的变量。
注意:var不能作为类的成员变量(类的成员变量按照规划声明处是不赋值的),只能用于方法内部的临时变量。var必须初始化。
var的缺点:
降低了程序的可读性。
大括号设置对象初始值
在实例化一个对象时可以直接在后面添加大括号,并在大括号中对字段/属性赋值。
构造函数的小括号此时可以省略。
匿名类型
var变量可以声明为自定义的匿名类型。
//内部字段随意
var tempClass = new {
age = 10,
money = 11,
}
可空类型
符号
?
对于值类型而言使用?,值类型可以等于null,但同时意味着该值类型变成了Nullable类型,所以不能再直接得到其值,需要使用value属性来获得。
对于引用类型使用?会将其作为一种语法糖,其能够在我们对引用类型进行值相关操作时帮助我们自动判断引用类型是否为空。效果等价于一个if判空。
下面为一些例子:
object o = null;
/*if(o != null)
{
o.ToString();
}*/
o?.ToString();
//上下等价
int[] arrayInt = null;
Console.WriteLine(arrayInt?[0]);
Action action = null;
/*
if(action != null){
action();
}
*/
action?.Invoke();
//上下等价
空合并操作符
空合并操作符
??
应用
左边值 ?? 右边值; / /如果左边为空就返回右边值,否则返回左边值。
注意
使用此操作符必须保证两边都可以为空。
应用
可以在特殊情况下代替三目运算符
int? intV = null;
//int int1 = intV == null ? 100 : intV.Value;
int int1 = intV ?? 100;
内插字符串
关键符号
$
作用
使用$来构造字符串,可以让字符串直接拼接变量。
string name = "123";
Console.WriteLine($"好好学习,{name}");
单句逻辑简略写法
- 当循环或者if语句中只有一句代码时,大括号可以省略。
- 对于方法,如果其内部只有一句代码,则可以这样写:
public int Add(int a,int b) => a + b;
值类型和引用类型补充
结构体继承接口存在拆装箱操作。
结构体只能通过()强转,不能通过as 转换类型,as只适用于类。