简单数据结构类

Arraylist

本质:Arraylist是一个C#为我们封装好的类,它的本质是一个object类型的数组,Arraylist类帮助我们实现很多方法:数组的增删查改等等。

using System.Collections; //需要引用命名空间using System.Collections; ArrayList array = new ArrayList(); //1.增(因为是object类型,所以可以存任何类型的数据) array.Add(1); array.Add("123"); array.Add(true); array.Add(new object()); array.Add(new Test()); //范围增加(批量增加,把另一个list容器里面的内容加到后面) ArrayList array2 = new ArrayList(); array2.Add(123); array.AddRange(array2); //增加到指定位置 array2.Insert(1,"123456"); Console.WriteLine(array[1]); //2.删 //移除指定元素 从头找 找到删 array.Remove(1); //移除指定位置的元素 array.Remove(2); //删object array.Clear(); //3.查 //得到指定位置的元素 Console.WriteLine(array[0]); //查看元素是否存在 if(array.Contains("123")) Console.WriteLine("存在123"); //正向查找元素位置 找到返回值是位置 找不到返回-1 int index = array.IndexOf(true); Console.WriteLine(index); //1 Console.WriteLine(array.IndexOf(false)); //-1 //反向查找LastIndexOf //4.改 Console.WriteLine(array[0]); array[0] = "999"; Console.WriteLine(array[0]); Console.WriteLine(array.Count); //7 Console.WriteLine(array.Capacity); //16 for(int i = 0; i < array.Count; i++) Console.WriteLine(array[i]); //迭代器遍历 foreach (object item in array) Console.WriteLine(item);

ArrayList本质上是一个可以自动扩容的object数组,由于用万物之父来储存数据,自然存在装箱拆箱。

当其中进行值类型存储时就是在装箱,当将值类型对象取出来转换使用时,就存在拆箱。

int i = 1;
array[0] = i; //装箱
i = (int)array[0]; //拆箱

所以ArrayList尽量少用,之后会学习更好的数据容器。

但也不是不能用,ArrayList的优点是object可以存储任何类型的数据。

Stack

Stack也是一个c#为我们封装好的类,它的本质也是object[ ]数组,只是封装了特殊的存储规则。

Stack是栈储存容器,栈是一种先进后出的数据结构。

using System.Collections; //需要引用命名空间using System.Collections; Stack stack = new Stack(); //增删查改 stack.Push(1); stack.Push(true); stack.Push(1.2f); stack.Push(new Test()); //栈中不存在删除的概念,只有取 //弹栈(取) object v = stack.Pop(); Console.WriteLine(v); //Test里的东西 object v = stack.Pop(); Console.WriteLine(v); //1.2f //栈无法查看指定元素的内容,只能看栈顶的内容 object v = stack.Peek(); Console.WriteLine(v); //true object v = stack.Peek(); Console.WriteLine(v); //true //查看元素是否存在于栈中 if(stack.Contains("123")) Console.WriteLine("存在123"); //栈无法改变其中的元素 只能压栈(存) 弹栈(取) //实在要改 只有清空 stack.Clear(); stack.Push("1"); stack.Push(2); stack.Push("哈哈哈"); Console.WriteLine(stack.Count); //用foreach遍历,而且遍历出来的顺序也是从栈顶到栈底 foreach (object item in stack) Console.WriteLine(item); //不可以直接用for循环遍历,需要将栈转化为object数组 //遍历出来的顺序也是从栈顶到栈底 object[] array = stack.ToArray(); for(int i = 0;i < array.Length;i++) Console.WriteLine(array[i]); //循环弹栈 即边取边用 Console.WriteLine(stack.Count); //3 while(stack.Count > 0) object o = stack.Pop(); Console.WriteLine(o); Console.WriteLine(stack.Count); //0

ArrayList本质上是一个可以自动扩容的object数组,由于用万物之父来储存数据,自然存在装箱拆箱。

当其中进行值类型存储时就是在装箱,当将值类型对象取出来转换使用时,就存在拆箱。

Queue

Queue也是一个c#为我们封装好的类,它的本质也是object[ ]数组,只是封装了特殊的存储规则。

Queue是队列储存容器,栈是一种先进先出的数据结构。

//和栈基本一样
Queue queue = new Queue();
queue.Enqueue(1);
Queue.Enqueue("123");
Queue.Enqueue(1.4f);
Queue.Enqueue(new Test());
object v = queue.Dequeue();
Console.WriteLine(v);
v = queue.Peek();
Console.WriteLine(v);
queue.Clear();
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
Console.WriteLine(queue.Count);
foreach (object item in queue)
    Console.WriteLine(item);
object[] array = queue.ToArray();
for(int i = 0;i < array.Length;i++)
    Console.WriteLine(array[i]);
Console.WriteLine(queue.Count); //3
while(queue.Count > 0)
    object o = queue.Pop();
    Console.WriteLine(o);
Console.WriteLine(queue.Count); //0

ArrayList本质上是一个可以自动扩容的object数组,由于用万物之父来储存数据,自然存在装箱拆箱。

当其中进行值类型存储时就是在装箱,当将值类型对象取出来转换使用时,就存在拆箱。

Hashtable

  • Hashtable是基于键的哈希代码组织起来的键、值对
  • 它的在就主要作用是提高数据查询的效率
  • 使用键来访问集合中的元素
  • //需要引用命名空间using System.Collections; Hashtable hashtable = new Hashtable(); //增删查改 //增 注意不能出现相同的键 hashtable.Add(1,"123"); hashtable.Add("123",2); hashtable.Add(true,false); hashtable.Add(false,true); //删 只能通过键去删除 hashtable.Remove(1); //删除不存在的键没反应 hashtable.Remove(2); //直接清空 hashtable.Clear(); hashtable.Add(1,"123"); hashtable.Add(2,"1234"); hashtable.Add(3,"123"); hashtable.Add("123",12); //1.通过键值查找 找不到会返回空 Console.WriteLine(hashtable[1]); Console.WriteLine(hashtable[2]); //null Console.WriteLine(hashtable["123"]); //2.查看是否存在 //根据键检测 if(hashtable.Contains(2)) Console.WriteLine("存在键为2的键值对"); if(hashtable.ContainsKey(2)) Console.WriteLine("存在键为2的键值对"); //根据值检测 if(hashtable.ContainsValue(12)) Console.WriteLine("存在值为12的键值对"); //改 只能修改键对应值的内容,无法修改键 Console.WriteLine(hashtable[1]); hashtable[1] = 100.5f; Console.WriteLine(hashtable[1]); //得到键值对 对数 Console.WriteLine(hashtable.Count); //1.遍历所有键 foreach (object item in hashtable.Key) Console.WriteLine("键:" + item); Console.WriteLine("值:" + hashtable[item]); //2.遍历所有值 foreach (object item in hashtable.Values) Console.WriteLine("值:" + item); //3.键值对一起遍历 foreach (DictionaryEntry item in hashtable.Key) Console.WriteLine("键:" + item.Key); Console.WriteLine("值:" + item.Value); //4.迭代器遍历法 IDictionaryEnumerator myEnumerator = hashtable.GetEnumerator(); bool flag = myEnumerator.MoveNext(); while(flag) Console.WriteLine("键:" + myEnumerator.Key + "值:" + myEnumerator.Value); flag = myEnumerator.MoveNext();
    • 泛型实现了类型参数化,达到代码重用的目的,通过类型参数化来实现同一份代码上操作多种类型
    • 泛型相当于类型占位符,定义类或方法时使用替代符代表变量类型,当真正使用类或者方法时再具体指定类型
    //泛型分类:
    //1.泛型类和泛型接口
    //class 类名<泛型占位字母>
    class TestClass<T>
        public T value;
    //泛型占位符可以有多个
    class TestClass2<T1,T2,K,LL,M>
        public T1 value1;
        public T3 value2;
        public K value3;
        public LL value4;
        public M value5;
    //interface 接口名<泛型占位字母>
    interface TestInterface<T>
        T value
    class Test:TestInterface<int>
        public int value
    //2.泛型函数
    //函数名<泛型占位字母>(参数列表)
    //2.1普通类中的泛型方法
    class Test2 
        public void Testfun<T>(T value)
            Console.WriteLine(value);
        public void Testfun<T>()
            //用泛型在里面做一些逻辑处理
            T t = default(T); //不可以赋值0或null之类,用default能获得类型默认值
        public void Testfun<T>(string v)
            return default(T);
        public void Testfun<T,K,M>(T t,K k,M m)
    //2.2泛型类中的泛型方法
    class Test2<T>
        //不是泛型方法 T是类中定义的
        /*public void Testfun<T>(T value)
            Console.WriteLine(value);
        public void Testfun<K>(K k)
            Console.WriteLine(k);
    
    TestClass<int> t = new TestClass<int>();
    t.value = 10;
    Console.WriteLine(t.value);
    TestClass<string> t = new TestClass<string>();
    t.value = "123123";
    Console.WriteLine(t.value);
    class TestClass2<int,string,float,TestClass<int>,uint> = new TestClass2<int,string,float,TestClass<int>,uint>();
    Test2 tt = new Test2();
    tt.TestFun<string> ("123123"); //输出123123
    

    概念:让泛型的类型有一定的限制

    关键字:where

    泛型约束一共有六种:

    • 1.值类型 where 泛型字母:struct
    • 2.引用类型 where 泛型字母:class
    • 3.存在无参公共构造函数 where 泛型字母:new()
    • 4.某个类本身或者其派生类 where 泛型字母:类名
    • 5.某个接口的派生类型 where 泛型字母:接口名
    • 6.另一个泛型类型本身或者派生类型 where 泛型字母:另一个泛型字母

    where 泛型字母 : (约束的类型)

    //各种泛型约束讲解
    //1.值类型约束
    class Test1<T> where T:struct
        public T value;
        public void TestFun<K>() where K:struct
    //引用是不可用null值的数据类型,必须是值类型
    Test1<int> t1 = new Test1<int>();
    t1.TestFun<float>(1.3f);
    //2.引用类型约束
    class Test2<T> where T:class
        public T value;
        public void TestFun<K>() where K:class
    Test2<Random> t2 = new Test2<Random>();
    t2.value = new Ramdom();
    t2.TestFun<object>(new object());
    //3.公共无参构造约束
    class Test3<T> where T:new()
        public T value;
        public void TestFun<K>(K k) where K:new()
    class Test1
        //默认有无参构造函数
    class Test2
        public Test2(int a);
    class Test3
        private Test3();
    Test3<Test1> t3 = new Test3<Test1>();
    //Test3<Test2> t3 = new Test3<Test2>(); //会报错 有参构造函数会把无参构造函数顶掉
    //Test3<Test3> t3 = new Test3<Test3>(); //会报错 必须公共无参构造函数
    //还需要非抽象类 因为抽象类无法被new
    //4.类约束
    class Test4<T> where T:Test1
        public T value;
        public void TestFun<K>(K k) where K:Test1
    class Test3 : Test1
    Test4<Test1> t4 = new Test3<Test1>();
    Test4<Test3> t4 = new Test3<Test3>();
    //Test4<Test2> t4 = new Test3<Test2>(); //会报错 不是Test1或其派生类 父类也不行
    //5.接口约束
    interface IFly
    interface Test5 : IFly
    class Test5<T> where T : IFly
        public T value;
        public void TestFun<K>(K k) where K : IFly
    Test5<IFly> t5 = new Test5<IFly>();
    //不能new IFly 但可以用里氏替换原则
    t5.value = new Test5();
    //也可以直接Test5<Test5> t5 = new Test5<Test5>();
    //6.另一个泛型约束
    class Test6<T,U> where T : U
        public T value;
        public void TestFun<K>(K k) where K : U
    Test6<Test5,IFly> t6 = new Test6<Test5,IFly>();
    Test6<Test5,Test5> t6 = new Test6<Test5,Test5>();
    //约束的组合使用
    class Test7<T> where T: class,IFly
    //多个泛型有约束
    class Test8<T,K> where T:class,new() where K:struct
    

    常用泛型数据结构类

    概念:List是一个C#为我们封装好的类,它的本质是一个可变类型的泛型数组,List类实现了很多方法如:泛型数组的增删查改。

    //using System.Collections.Generic List<int> list = new List<int>(); List<string> list2 = new List<string>(); List<bool> list3 = new List<bool>(); //增删查改 list.Add(1); list.Add(2); list.Add(3); list.Add(4); list2.Add("123"); List<string> listStr = new List<string>(); ListStr.Add("123"); list.AddRange(listStr); //1.移除指定元素 list.Remove(1); //2.移除指定位置的元素 list.RemoveAt(0); //3.清空 list.clear(); list.Add(1); list.Add(2); list.Add(3); list.Add(4); //1.得到指定位置的元素 Console.WriteLine(list[0]); //2.查看元素是否存在 if(list.Contains(1)) Console.WriteLine(1); //3.正向查找元素位置 找到返回值是位置 找不到返回-1 int index = list.IndexOf(1); Console.WriteLine(index); //0 Console.WriteLine(array.IndexOf(5)); //-1 //4.反向查找LastIndexOf int index = list.IndexOf(2); Console.WriteLine(index); //1 //4.改 Console.WriteLine(list[0]); //1 list[0] = 99; Console.WriteLine(list[0]); //99 Console.WriteLine(list.Count); //4 Console.WriteLine(array.Capacity); //8 for(int i = 0; i < list.Count; i++) Console.WriteLine(list[i]); //迭代器遍历 foreach (object item in list) Console.WriteLine(item);

    Dictionary

    和hashtable基本一样,可以理解为拥有泛型的hashtable,它也是基于键的哈希代码组织起来的,键、值对。

    键值对类型从Hashtable的object变为了可以自己制定类型的泛型。

    //using System.Collection.Generic Dictionart<int,string> dictionary = new Dictionary<int,string>(); //增删查改 dictionary.Add(1,"123"); dictionary.Add(2,"222"); dictionary.Add(3,"222"); //1.删 只能通过键去删除 dictionary.Remove(1); //删除不存在的键没反应 dictionary.Remove(4); //2.直接清空 dictionary.Clear(); dictionary.Add(1,"123"); dictionary.Add(2,"222"); dictionary.Add(3,"222"); //1.通过键值查找 找不到会直接报错 Console.WriteLine(dictionary[1]); //Console.WriteLine(dictionary[4]); //会报错 //2.查看是否存在 找不到就返回false //根据键检测 和哈希表不一样只有一个函数 if(dictionary.ContainsKey(2)) Console.WriteLine("存在键为2的键值对"); //根据值检测 if(dictionary.ContainsValue(123)) Console.WriteLine("存在值为123的键值对"); //改 只能修改键对应值的内容,无法修改键 Console.WriteLine(dictionary[1]); dictionary[1] = "555"; Console.WriteLine(dictionary[1]); //得到键值对 对数 Console.WriteLine(dictionary.Count); //1.遍历所有键 foreach (int item in dictionary.Key) Console.WriteLine("键:" + item); Console.WriteLine("值:" + dictionary[item]); //2.遍历所有值 foreach (string item in dictionary.Values) Console.WriteLine("值:" + item); //3.键值对一起遍历 foreach (KeyValue<int,string> item in dictionary) Console.WriteLine("键:" + item.Key); Console.WriteLine("值:" + item.Value);

    顺序储存和链式储存

    顺序存储:用一组地址连续的存储单元一次存储线性表的各个数据元素。

    链式存储:用一组任意的存储单元存储线性表的各个数据元素。

    从增删查改的角度思考顺序存储和链式存储的优缺点:

    增:链式存储 计算上 优于顺序存储 (中间插入时链式不用像顺序一样去移动位置)

    删:链式存储 计算上 优于顺序存储 (中间删除时链式不用像顺序一样去移动位置)

    查:顺序存储 使用上 优于链式存储 (数组可以直接通过下标得到元素,链式需要遍历)

    改:顺序存储 使用上 优于链式存储 (数组可以直接通过下标得到元素,链式需要遍历)

Linkedlist

Linkedlist是一个c#为哦我们封装好的类,它的本质是一个可变类型的双向链表。

//using System.Collection.Generic LinkedList<int> linkedList = new LinkListed<int>(); //链表对象需要掌握两个类:LinkedList和LinkedListNode //增删查改 //1.在链表尾部添加元素 linkedList.AddLast(10); //2.在链表头部添加元素 linkedList.AddFirst(20); //3.在某一个节点之后添加一个节点 要在指定节点 先得得到一个节点 linkedListNode<int> n = LinkedList.Find(20); linkedList.addAfter(n,15); //4.在某一个节点之前添加一个节点 要在指定节点 先得得到一个节点 linkedList.addBefore(n,11); //1.移除头节点 linkedList.RemoveFirst(); //2.移除尾结点 linkedList.RemoveLast(); //3.移除指定节点 无法通过指定位置直接移除 linkedList.Remove(20); //4.清空 linkedList.Clear(); linkedList.AddLast(1); linkedList.AddLast(2); linkedList.AddLast(3); linkedList.AddLast(4); //1.头节点 linkedListNode<int> first = LinkedList.First; //1 //2.尾结点 linkedListNode<int> last = LinkedList.Last; //4 //3.找到指定值的节点 无法通过下标 只有遍历查找指定位置的元素 linkedListNode<int> node = LinkedList.Find(3); //去找值为3的节点 Console.WriteLine(node.Value); node = linkedList.Find(5); //找不到会返回空 //4.判断是否存在 if(linkedLine.Contains(1)) Console.WriteLine("链表中存在1"); //要先得到节点 再改变其中的值 Console.WriteLine(linkedList.First.Value); linkedList.First.Value = 10; Console.WriteLine(linkedList.First.Value); //1.foreach遍历 foreach (int item in linkedList) Console.WriteLine(item); //2.通过节点遍历 //从头到尾 LinkedListNode<int> nowNode = linkedList.First; while(nowNode != null) Console.WriteLine(nowNode.Value); nowNode = nowNode.Next; //从尾到头 nowNode = linkedList.Last; while(nowNode != null) Console.WriteLine(nowNode.Value); nowNode = nowNode.Previous;

泛型栈和队列

使用上和之前的Stack和Queue一模一样

//using System.Collection.Generic
Stack<int> stack = new Stack<int>();
Queue<int> queue = new Queue<int>()

委托和事件

委托是函数(方法)的容器,可以理解为表示函数的变量类型。用来存储,传递函数(方法)。

委托的本质是一个类,用来定义函数(方法)的类型(返回值和参数的类型)。

不同的 函数(方法)必须对应各自“格式”一直的委托。

关键字:delegate

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

写在哪里?

可以申明再namespace和class语句块中 更多的写在namespace中

简单记忆委托语法 就是函数申明语法前面加一个delegate关键字

定义自定义委托:

访问修饰符默认不写为public 在别的命名空间中也能使用

private 其他命名空间就不能用了

一般使用public。

//申明了一个可以用来储存无参无返回值函数的容器
//这里只是定义了规则 并没有使用
delegate void MyFun();
//委托规则的申明是不能重名(同一语句块中)
//表示用来装载或传递 返回值是int 有一个int参数的函数的 委托 容器规则
delegate int MyFun2(int a);

使用定义好的委托

委托变量是函数的容器

static void Main(string[] args)
    MyFun f = new MyFun(Fun);
    Console.WriteLine("1");
    Console.WriteLine("2");
    Console.WriteLine("3");
    Console.WriteLine("4");
    Console.WriteLine("5");
    f.Invoke(); //123123
    MyFun f2 = Fun; //与MyFun f2 = new MyFun(Fun)一样
    Console.WriteLine("1");
    Console.WriteLine("2");
    Console.WriteLine("3");
    Console.WriteLine("4");
    Console.WriteLine("5");
    f2(); //123123
    MyFun2 f3 = Fun2;
    Console.WriteLine(f3.(1));
    MyFun2 f4 = new MyFun2(Fun2);
    Console.WriteLine(f4.Invoke(3));
static void Fun()
    Console.WriteLine("123123");
static int Fun2(int value)
    return value;
static void Fun3()
    Console.WriteLine("李四在做什么");
static string Fun4()
    return "";
static int Fun5()
    return 1;

委托常用在:

1.作为类的成员

2.作为函数的参数

class Test
    public MyFun fun;
    public MyFun2 fun2;
    public MyFun(MyFun fun,MyFun2 fun2)
        //先处理一些别的逻辑 当这些逻辑处理完了 再执行传入的函数
        int i = 1;
        i *= 2;
        i += 2;
        this.fun = fun;
        this.fun2 = fun2;
Test t = new Test();
t.TestFun(Fun,Fun2);

委托变量可以存储多个函数(多播委托)

MyFun ff = Fun;
ff += Fun;
ff();  //会输出两次123123

使用系统自带的委托

//using System;就可以使用Action 无参无返回值的委托
Action action = Fun;
action += Fun;
action();
//系统给我们自带的返回值为<>里面的类型的委托
Func<string> funcString = Fun4;
Func<int> funcInt = Fun5;
//可以传n个参数的 系统提供了1到16个参数的委托 直接用就行
Action<int,string> = action2 Fun6;
//可以传n个参数的 并且有返回值的 系统也提供了16个委托
Func<int,int> func2 = Fun2;
//自己写一个泛型委托
delegate T MyFun3<T,K>(T t,K k);

事件是基于委托的存在,事件是委托的安全包裹。让委托的使用更具安全性,事件是一种特殊的变量类型。

访问修饰符 event 委托类型 事件名

事件的使用:

  • 1.事件是作为成员变量存在于类中
  • 2.委托怎么用 事件就怎么用

事件相对于委托的区别:

  • 1.不能再类外部赋值
  • 2.不能再类外部调用

注意:它只能作为成员存在于类和接口以及结构体中

class Test
    //委托成员变量 用于存储 函数的
    public Action myFun;
    //事件成员变量 用于存储 函数的
    public event Action myEvent;
    public Test()
        //事件的使用和委托一模一样 只是有些细微的区别
        myFun = TestFun;
        myFun += TestFun;
        myFun -= TestFun;
        myFun();
        myFun.Invoke();
        myFun = null;
        myEvent = TestFun;
        myEvent += TestFun;
        myEvent -= TestFun;
        myEvent();
        myEvent.Invoke();
        myEvent = null;
    //如果真的想在外部调用事件 就要在类的内部封装一个方法
    public void DoEvent()
        if(myEvent != null)
            myEvent();
    public void TestFun()
        Console.WriteLine("123123");
Test t = new Test();
//委托可以再外部赋值
t.myFun = null;
t.myFun = TestFun2;
//事件是不能再外部赋值的
//t.myEvent = null;
//t.myEvent = TestFun2;
//虽然不能直接赋值,但是可以+- 去添加移除记录的函数
t.myEvent += TestFun2;
t.myEvent -= TestFun2;
//事件不可以直接赋值
//t.myEvent = t.myEvent + TestFun2;
//委托是可以再外部调用的
t.myFun.Invoke();
t.myFun();
//事件是不能再外部调用的
//t.myEvent();
//t.myEvent.Invoke();
t.DoEvent();
Action a = TestFun2;
//事件是不能作为临时变量在函数中使用的
//event Action ae = TestFun2;
static  void TestFun2()

为什么有事件?

  • 1.防止外部随意置空委托
  • 2.防止外部随意调用委托
  • 3.事件相当于对委托进行了一次封装 让其更安全
  • 顾名思义,就是没有名字的函数。

    匿名函数的使用主要是配合委托和事件进行使用。

    脱离委托和事件是不会使用匿名函数的。

    delegate (参数列表)

    ​ 函数逻辑

    何时使用?

    • 函数中传递委托参数时
    • 委托或事件赋值时

    匿名函数的使用

    //1.无参无返回
    //这样申明匿名函数 只是在申明函数而已 还没有调用
    Action a = delegate()
        Console.WriteLine("匿名函数逻辑");
    }; //记住要分号
    //这样才是真正调用匿名函数
    //2.有参
    Action<int,string> b = delegate(int a,string b)
        Console.WriteLine(a);
        Console.WriteLine(b);
    b(100,"123");
    //3.有返回值
    Fanc<string> c = delegate()
        return "123";
    //4.一般情况会作为函数参数传递或者作为函数返回值
    Test t = new Test();
    //参数传递
    Action ac = delegate()
        Console.WriteLine("随参数传入的匿名函数逻辑");
    t.Dosomething(100,ac);
    t.Dosomething(100, delegate()
                      Console.WriteLine("随参数传入的匿名函数逻辑");
    //返回值
    Action ac2 = t.GetFun();
    ac2();
    //一步到位
    t.GetFun()();
    
    class Test
        public Action action;
        //作为参数传递时
        public void DOsomething(int a,Action fun)
            Console.WriteLine(a);
            fun();
        //作为返回值
        public Action GetFun()
            return delegate()
                Console.WriteLine("函数内部返回的一个匿名函数的逻辑");
    

    匿名函数的缺点

    • 添加到委托或事件容器中后 不记录 无法单独移除
    Action ac3 = delegate()
        Console.WriteLine("匿名函数一");
    ac3 += delegate()
        Console.WriteLine("匿名函数二");
    ac3();
    //会输出匿名函数一 匿名函数二
    //因为匿名函数没有名字 所以没有办法指定移除某一个匿名函数
    

    Lambad表达式

    可以将Lambad表达式理解为匿名函数的简写。

    它除了写法不同外,使用上几乎和匿名函数一模一样,都是和委托或者事件配合使用的、

    //lambad表达式
    //(参数列表)=>
    //{  函数体  };
    //1.无参无返回值
    Action a = ()=>
        Console.WriteLine("无参无返回值的lambad表达式");
    //2.有参
    Action<int> b = (int value) =>
        Console.WriteLine("有参数的lambad表达式{0}",value);
    b(100);
    //3.甚至参数类型可以省略 参数类型和委托或事件容器一致
    Action<int> c = (value) =>
        Console.WriteLine("省略参数类型的lambad表达式{0}",value);
    c(200);
    //4.有返回值
    //Func<>最后一个类型是返回类型,前面都是参数
    Func<string,int> d = (value)=>
        Console.WriteLine("有返回值的lambad表达式{0}",value);
        return 1;
    d("123123");
    //其它传参使用等和匿名函数一样
    //缺点也和匿名函数一样的
    

    内层函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。

    注意:该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。

    class Test
        public event Action action;
        public Test()
            int value = 10;
            //这里就形成了闭包 
            //因为当构造函数执行完毕时 其中申明的临时变量value的声明周期被改变了
            action = ()=>
                Console.WriteLine(value)
            for(int i=0;i<10;i++)
                action += ()=>
                    Console.WriteLine(i);
            for(int i=0;i<10;i++)
                int index = i;
                action += ()=>
                    Console.WriteLine(index);
        public void DoSomething()
            action();
    
    Test t = new Test();
    t.DoSomething();
    //打印出来11个10
    //再y打印1-9
    

    List排序

    List自带的排序方法

    List<int> list = new List<int>();
    List.Add(3);
    List.Add(2);
    List.Add(6);
    List.Add(1);
    List.Add(4);
    List.Add(5);
    for(int i = 0;i < list.Count; i++)
        Console.WriteLine(list[i]);
    //list提供了排序方法 升序排列
    list.Sort();
    for(int i = 0;i < list.Count; i++)
        Console.WriteLine(list[i]);
    //123456
    //ArrayList也自带Sort方法
    

    自定义类的排序

    class Item : ICpmparable<Item>
        public int money;
        public Item(int money)
            this.money = momey;
        public int CompareTo(Item other)
            //返回值的含义
            //小于0:放在传入对象的前面
            //等于0:保持当前的位置不变
            //大于0:放在传入对象的后面
            //可以简单理解传入对象的位置 就是0
            //如果你的返回为负数,就放在它的左边 也就是前面
            //如果你返回正数 就放在它的右边 也就是后面
            //升序排列
            if(this.money>other.money)
                return 1;
                return -1;
    
    List<Item> itemList = new List<Item>();
    itemList.Add(new Item(45));
    itemList.Add(new Item(10));
    itemList.Add(new Item(99));
    itemList.Add(new Item(24));
    itemList.Add(new Item(100));
    itemList.Add(new Item(12));
    //排序方法
    itemList.Sort();
    for(int i = 0;i < itemList.Count; i++)
        Console.WriteLine(itemList[i].money);
    

    通过委托函数排序

    class ShopItem
        public int id;
        public ShopItem(int id)
            this.id = id;
    
    List<ShopItem> shopItems = new List<ShopItem>();
    shopItems.Add(new ShopItem(2));
    shopItems.Add(new ShopItem(1));
    shopItems.Add(new ShopItem(4));
    shopItems.Add(new ShopItem(3));
    shopItems.Add(new ShopItem(5));
    shopItems.Add(new ShopItem(6));
    shopItems.Sort(SortShopItem);
    for(int i = 0;i < shopItems.Count; i++)
        Console.WriteLine(shopItems[i].id);
    
    static int SortShopItem( ShopItem a,ShopItem b)
        //传入的两个对象为列表中的两个对象
        //进行两两的比较 用左边的和右边的条件比较
        //返回值规则和之前的一样 0做标准 负数在左(前) 正数在右
        if(a.ad>b.id)
            return 1;
            return -1;
    

    还可以直接用匿名函数传入

    shopItems.Sort(delegate (ShopItem a,ShopItem b)
                       if(a.ad>b.id)
                           return 1;
                           return -1;
    //lambad配合三目运算符 完美呈现
    shopItems.Sort((a,b) =>
                       return a.ad>b.id?1:-1;
    

    协变:和谐的变化,自然的变化。因为里氏替换原则,父类可以装子类,所以 子类变父类,比如string变成object。

    感受是和谐的

    逆变:逆常规的变化,不正常的变化。因为里氏替换原则,父类可以装子类,但是子类不能装父类。所以 父类变子类,

    比如object变成string感受是不和谐的。

    协变和逆变是用来修饰泛型的

    协变:out

    逆变:in

    用于泛型中 修饰 泛型字母的

    只有泛型接口和泛型委托能使用的

    //1、返回值和参数 //用out修饰的泛型 只能作为返回值 delegate T TestOut<out T>(); //括号里面不能写T类型参数了 //用in修饰的泛型 只能作为参数 delegate void TestIn<in T>(T t); interface Tesr<out T> T TestFun(); //2.结合里氏替换原则理解 class Father class Son:Father
    //协变 父类总是能被子类替换
    //看起来 就是son ->father
    TestOut<Son> os = ()=>
        return new Son();
    TestOut<Father> of = os;
    Father f= of(); //实际上返回的是 os里面装的函数返回的是Son
    //逆变 父类总能被子类替换
    TestIn<Father> iF = (value) =>
    TestIn<Son> iS = iF;
    iS(new Son()); //实际调用的是iF
    

    了解线程前先了解进程

    • 进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。打开一个应用程序就是在操作系统上开启了一个进程。

    进程之间可以相互独立运行,互不干扰。

    进程之间也可以相互访问、操作。

    什么是线程?

    • 操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
    • 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程。

    简单理解线程: 就是代码从上到下运行的一条管道。

    什么是多线程?

    • 我们可以通过代码 开启新的线程。可以同时运行代码的多条“管道”就叫做多线程
    • 线程类 Thread

      需要引用命名空间 using System.Threading;

      static bool isRuning = true;
      //1.申明一个新的线程
      // 注意 线程执行的代码 需要封装到一个函数中
      // 新线程 将要执行的代码逻辑 被封装到了一个函数语句块中
      Thread t = new Thread(NewThreadLogic); 
      //2.启动线程
      t.Start();
      //3.设置为后台线程
      //当前台线程都结束了的时候,整个程序也就结束了,即使还有后台线程正在运行
      //后台线程不会防止应用程序的进程被终止掉
      //如果不设置为后台线程 可能导致进程无法正常关闭
      t.IsBackground = true; //设置为后台线程
      //4.关闭释放一个线程
      //如果开启的线程中不是死循环 是能够结束的逻辑 那么 不用刻意地去关闭它
      //如果是死循环 想要终止这个线程 有两种方式
      //4.1 死循环中的bool标识
      Console.ReadKey();
      isRuning = false;
      Console.ReadKey();
      //4.2 通过线程提供的方法(注意在.Net core 版本中无法终止 会报错)
          t.Abort();
          t = null;
      catch
      //5.线程休眠
      //让线程休眠多少毫秒 1s=1000ms
      //在哪个线程里执行 就休眠哪个线程
      //Thread.Sleep(1000);
      
      static void NewThreadLogic()
          //新开线程 执行代码的逻辑 在该函数语句块中
          while(isRuning)
               Thread.Sleep(1000);
               Console.WriteLine("新开线程代码逻辑");
      

      线程之间共享数据

      • 多个线程使用的内存是共享的,都属于该应用程序(进程)
      • 所以要注意,当多线程同时操作同一片内存区域时可能会出问题
      • 可以通过加锁的形式避免问题。
      static object obj = new object();
      //lock 当我们在多个线程当中想要访问同样的东西 进行逻辑处理时
      //为了避免不必要的逻辑顺序执行的查错
      //lock(引用对象)
      while(true)
          lock(obj)
               Console.SetCursorPosition(0,0);
               Console.ForegroundColor = ConsoleColor.Red;
               Console.Write("⚪");
      static void NewThreadLogic()
          lock(obj)
              Console.SetCursorPosition(0,0);
              Console.ForegroundColor = ConsoleColor.Yellow;
              Console.Write("方形");
      

      多线程对于我们意义

      • 可以用多线程专门处理一些复杂耗时的逻辑
      • 比如寻路、网络通信等等

      预处理器指令

      什么是编译器?

      • 编译器是一种翻译程序,它用于将源语言程序翻译为目标语言程序
      • 源语言程序:某种程序设计语言写成的,比如C#、C++、JAVA等语言写的程序
      • 目标语言程序:二进制数表示的伪辑器代码写的程序。

      什么是预处理指令?

      • 预处理指令 指导编译器 在实际编译开始之前对信息进行预处理
      • 预处理指令 都是以#开始
      • 预处理指令不是语句,所以它们不以分号;结束
      • 目前我们经常用到的 折叠代码块 就是预处理器指令

      常见的预处理指令

      1.#define 定义一个符号,类似一个没有值的变量

      undef 取消define定义的符号,让其失效。

      两者一般都写在脚本文件最前面。一般配合if指令使用,或配合特性

      //定义一个符号
      #define Unity4
      #define IOS
      #define Unity2021  
      //取消定义一个符号
      #undef Unity4
      

      2.#if #elif #else #endif 和用if语句一样,一般配合# define定义的符号使用,用于告诉编译器进行编译代码的流程控制。

      如果发现有Unity4这个符号 那么其中包含的代码就会被编译器编译

      可以通过 逻辑或 和 逻辑与 进行多种符号的组合判断

      //如果发现有Unity4的符号 那么其中包含的代码 就会被编译器翻译
      #if Unity4
      Console.WriteLine("版本为Unity4"); //不会打印 因为前面把unity4取消了
      #elif Unity2021 && IOS
      Console.WriteLine("版本为Unity2021");   
      //#warning 这个版本不合法
      //#error z
      #else
      Console.WriteLine("其他版本");    
      #endif
      

      3.#warning #error 告诉编译器,是报警告还是报错误 一般还是配合if使用

      反射和特性

      什么是程序集?

      程序集是经由编译器编译得到的,供进一步编译执行的那个中间产物

      在WINDOWS系统中,它一般表现为后缀为.dll(库文件)或者.exe(可执行文件)的格式

      程序集就是我们写的一个代码集合,我们现在写的所有代码,最终都会被编译器翻译为一个程序集供别人使用

      比如一个代码库文件(dll)或者一个可执行文件(exe)。

      元数据就是用来描述数据的数据。

      这个概念不仅仅用于程序上,在别的领域也有元数据

      程序中的类,类中的函数、变量等等信息就是 程序的 元数据

      有关程序以及类型的数据被称为 元数据,它们保存在程序集中

      反射的概念

      程序正在运行时,可以查看其他程序集或者自身的元数据。

      一个运行的程序查看本身或者其它程序的元数据的行为就叫做反射。

      程序在运行时,通过反射可以得到其它程序集或者自己程序集代码的各种信息

      类、函数、变量、对象等等,实例化它们,执行它们,操作它们。

      反射的作用

      因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性。

      • 1.程序运行时得到所有元数据,包括元数据的特性
      • 2.程序运行时实例化对象,操作对象
      • 3.程序运行时创建新对象,用这些对象执行任务
      class Test
          private int i = 1;
          public int j = 1;
          public string str = "123";
          public Test()
          public Test(int i)
              this.i = i;
          public Test(int i,string str):str(i)
              this.str = str;
          public void Speak()
              Console.WriteLine(i);
      

      获取Type

      //Type(类下信息类)
      //它是反射功能的基础
      //它是访问元数据的主要方式
      //使用Type的成员获取有关类型声明的信息
      获取Type
      //1.万物之父object中的GetType()可以获取对象的Type
      int a = 42;
      Type type = a.GeyType();
      Console.WriteLine(type);
      //输出System.Int32
      //2.通过typeof关键字 传入类名 也可以得到对象的Type
      Type type2 = typeof(int);
      Console.WriteLine(type2);
      //输出System.Int32
      //3.通过类的名字 也可以获取类型
      // 注意:类名必须包括命名空间
      Type type3 = Type.GeyType("System.Int32");
      Console.WriteLine(type3);
      //输出System.Int32
      //type123它们的值和在堆里的地址都是一样的
      

      得到类的程序集信息

      //可以通过Type得到类型所在程序集信息
      Console.WriteLine(type.Assembly);
      Console.WriteLine(type2.Assembly);
      Console.WriteLine(type3.Assembly);
      //输出三次:System.Private.CoreLib, Version=4.0.0.0, Culture-neutral, PublicKeyToken=7cec85d7ba7798e
      //以上是版本信息
      

      获取类中的公共成员

      //首先得到Type
      Type t = typeof(Test);
      //然后得到所有公共成员
      //需要引用命名空间 using System.Reflection;
      MemberInfo[] infos = t.GetMembers();
      for(int i = 0;i < infos.Length; i++)
          Console.WriteLine(infos[i]);
      //会输出所有公共成员Void Speak()等等
      

      获取类的公共构造函数并调用

      //1.获取所有构造函数
      ConstructorInfo[] ctors = t.GetConstructors();
      for(int i = 0;i < ctors.Length; i++)
          Console.WriteLine(ctors[i]);
      //输出 Void .ctor()\n Void .ctors(Int32) \n Void .ctors(Int32,System String) 
      //2.获取其中一个构造函数传入Type数组 数组中内容按顺序是参数类型
      //得构造函数传入Type数组 数组中内容按顺序是参数类型
      //执行构造函数传入 object数组 表示按顺序传入的参数
      // 2-1得到无参构造
      ConstructorInfo info = t.GetConstructor(new Type[0]);
      //执行无参构造 无参构造 没有参数 传null
      Test obj = info.Invoke(null) as Test;
      Console.WriteLine(obj.j);
      // 2-2得到有参构造
      ConstructorInfo info2 = t.GetConstructor(new Type[]{typeof(int)});
      obj = info2.Invoke(new object[]{ 2 }) as Test;
      Console.WriteLine(obj.str);
      ConstructorInfo info3 = t.GetConstructor(new Type[]{typeof(int),typeof(string)});
      obj = info3.Invoke(new object[]{ 4,"44444" }) as Test;
      Console.WriteLine(obj.str);
      

      获取类的公共成员变量

      //1.得到所有成员变量
      FieldInfo[] fieldInfos = t.GetFields();
      for(int i = 0; i < fieldfos.Length; i++)
          Console.WriteLine(fieldInfos[i]);
      //输出 Int32 j System String str
      //2.得到指定名称的公共成员变量
      FieldInof infoJ = t.GetField("j");
      Console.WriteLine(infoJ);
      //输出 Int32 j
      //3.通过反射来获取和设置对象的值
      Test test = new Test();
      test.j = 99;
      test.str = "999";
      // 3-1 通过反射 获取对象的某个变量的值
      Console.WriteLine(infoJ.GetValue(test));
      // 3-2 通过反射 设置对象的某个变量的值
      infoJ.SetValue(test,100);
      Console.WriteLine(infoJ.GetValue(test));
      

      获取类的公共成员方法

      //通过Type类中的GetMethod公共 得到类中的方法
      //MethodInfo 是方法的反射信息
      Type strType = typeof(string);
      //1.如果存在方法重载 用Type数组表示参数类型
      MethodInfo[] methods = strType.GetMethods();
      for(int i = 0; i < methods.Length; i++)
          Console.WriteLine(methods[i]);
      MethodInfo subStr = strType.GetMethod("Substring",new Type[] {typeof(int),typeof(int)});
      //2.调用该方法
      string str = "Hello,World!";
      //注意:如果是静态方法 Invoke中的第一个参数传null即可
      object result = subStr.Invoke(str,new object[]{7,5});
      Console.WriteLine(result);
      //输出orld!
      

      Actovator

      • 用于快速实例化对象的类
      • 用于将Type对象快捷实例化为对象
      //先得到type
      //然后快速实例化一个对象
      Test testType = typeof(Test);
      //1.无参构造
      Test testObj = Activator.CreateInstance(testType) as Test;
      Console.WriteLine(testObj.str);
      //2.有参数构造
      testObj = Activator.CreateInstance(testType,99) as Test;
      Console.WriteLine(testObj.j); //i是私有的,没办法访问,但是可以通过断点看到i是99
      testObj = Activator.CreateInstance(testType,99,"111222") as Test;
      Console.WriteLine(testObj.j); 
      

      Assembly

      程序集类:主要用来加载其它程序集。加载后才能用Type来使用其他程序集中的信息,比如,dll文件(库文件),简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。

      //三种加载程序集的函数
      //一般用来加载同一文件下的其他程序集
      //sembly assembly2 = Assembly.Load("程序集名称");
      //一般用来加载不在同一文件下的其他程序集
      //sembly assembly = Assembly.LoadFrom("")
      //一般用来加载不在同一文件下的其他程序集
      //Assembly assembly = Assembly.LoadFrom("包含程序集清单的文件的名称或路径");
      //Assembly assembly2 = Assembly.LoadFile("要加载的文件完全限定路径");
      //1.先加载一个程序集
      //加一个@能够取消转义字符 找到dll文件复制路径+dll文件名
      Assembly assembly = Assembly.LoadFrom(@"E:\C#\ArrayList\ArrayList\bin\Debug\netcoreapp3.1\Lesson_18练习题"); 
      //得到元数据中所有的类型
      Type[] types = assembly.GetTypes();
      for(int i = 0; i < types.length; i++)
          Console.WriteLine(types[i]);
      //2.再加载程序集中的一个类对象 之后才能用反射
      Type icon = assembly.GetType("Lession_18练习题.Icon");
      MemberInfo[] members = icon.GetMembers();
      for(int i = 0; i < members.Length; i++)
          Console.WriteLine(members[i]);
      //通过反射 实例化一个icon
      //首先得到枚举Type 来得到可以传入的参数
      Type moveDir = assembly.GetType("Lession_18练习题.E_MoveDir");
      FieldInfo right = moveDir.GetField("Right");
      //直接实例化对象
      object iconObj = Activator.CreateInstance(icon,10,5,right.GetValue(null));
      //得到对象中的方法
      MethodInfo move = icon.GetMethod("Move");
      MethodInfo draw = icon.GetMethod("Draw");
      MethodInfo clear = icon.GetMethod("Clear");
      while(true)
          Thread.Sleep(1000); //休眠1s
          clear.Invoke(iocnObj,null);
          move.Invoke(iocnObj,null);
          draw.Invoke(iocnObj,null);
      //3.类库工程创建
      

      为什么要学反射?

      为了之后学习Unity引擎的基本工作原理做铺垫

      Unity引擎的基本工作机制 就是建立在反射的基础上

      特性是什么?

      • 1.特性是一种允许我们向 程序的程序集 添加 元数据 的语言结构
      • 它是用于保存程序结构信息的某种特殊类型的类
      • 2.特性提供功能强大的方法以将声明信息与 C# 代码(类型、方法、属性等)相关联。
      • 特性与程序实体关联后,即可在运行是使用反射查询特性信息。
      • 3.特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中。
      • 它可以放置在几乎所有的声明中(类、变量、函数等等申明)
        • 特性的本质是个类
        • 我们可以利用特性类为元数据添加额外信息
        • 比如一个类、成员变量、成员方法等等为他们添加更多额外信息
        • 之后可以通过反射来获取这些额外信息

        自定义特性

        //继承特性基类 Attribute
        class MyCustomAttribute : Attribute
            //特性中的成员 一般根据需求来写
            public string info;
            public MyCustomAttribute(string info)
                this.info = info;
            public void TestFun()
                Console.WriteLine("特性的方法");
        

        特性的使用

        基本语法:[特性名(参数列表)]

        本质上 就是在调用特性类的构造函数

        写在哪?类、函数、变量上一行,表示他们具有该特性信息

        [MyCustom("这是一个我自己写的用于计算的类")]
        class MyClass
            [MyCustom("这是一个成员变量")]
            public int value;
            [MyCustom("这是一个用于计算加法的函数")]
            public void TestFun([MyCustom("函数参数")]int a)
        
        //特性的使用
        MyClass mc = new MyClass();
        //复习下多种得到type方法
        Type t = mc.GetType();
        //t = typeof(MyClass);
        //t = Type.GetType("Lession21_特性.MyClass");
        //判断是否使用了某个特性
        //参数一:特性的类型   
        //参数二:代表是否继承链(属性和事件忽略此参数)
        if(t.IsDefined(typeof(MyCustomAttribute),false))
            Console.WriteKLine("该类型应用了特性");
        //获取Type元数据中的所有特性
        object[] array = t.GetCustomAttributes(true);
        for(int i = 0; i < array.Length; i++)
            if(array[i] is MyCustomAttribute)
                Console.WriteLine((array[i] as MyCustomAttribute).info);
                (array[i]  as MyCustomAttribute).TestFun();
        

        限制自定义特性的使用范围

        通过为特性类 加特性 限制其使用范围

        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,AllowMultiple = true,Inherited = true)]

        • 参数一:AttributeTargets —— 特性能够用在哪些地方
          参数二:AllowMultiple —— 是否允许多个特性实例用在同一个目标上
          参数三:Inherited —— 特性是否能被派生类和重写成员基础

        系统自带特性——过时特性

        过时特性 Obsolete

        • 用于提示用户 使用的方法等成员以及过时 建议使用新方法

        一般加在函数前的特性

        class TestClass
            //参数一:调用过时方法时 提示的内容
            //参数二:true——使用该方法时会报错 false——使用该方法时直接警告
            [Obsolete("OldSpeak方法已经过时了,请使用Speak方法"),false]
            public void OldSpeak(string str)
            public void Speak()
            //系统自带特性——调用者信息特性
            //系统会通过特性自动传入需要的信息
            public void SpeakCaller(string str,[CallerFilePath]string fileName = "",
                                   [CallerLineNumber]int line = 0,[CallerMemberName]string target = "")
        

        系统自带特性——调用者信息特性

        哪个文件调用? CallerFilePath特性

        哪一行调用? CallerLineNumber特性

        哪个函数调用?CallerMemberName特性

        需要引用命名空间 using System.Runtime.CompilerServices;

        一般作为函数参数的特性

        系统自带特性——条件编译特性

        条件编译特性 Conditional

        它会和预处理指令 #define 配合使用

        需要引用命名空间using System.Diagnostics;

        主要可以用在一些调试代码上

        有时想执行有时不想执行的代码

        #define Fun
        [Conditional("Fun")]
        static void Fun()
            Console.WriteLine("Fun执行");
        Fun(); //无法调用 只有加上#define Fun才能打印
        

        系统自带特性——外部Dll包函数特性

        DllImport

        用来标记非.Net(C#)的函数,表明该函数在一个外部的DLL中定义。

        一般用来调用C或者C++的DLL包写好的方法

        需要引用命名空间 using System.Runtime.InteropServices

        [DllImport("Test.dll")]
        public static extern int Add(int a,int b);
        

        迭代器是什么?

        • 迭代器有时又称光标,是程序设计的软件设计模式。
        • 迭代器提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的标识。
        • 在表现效果上看:
        • 是可以在容器对象(例如链表或数组)上遍历访问的接口
        • 设计人员无需关心容器对象的内存分配的实现细节
        • 可以用foreach遍历的类,都是实现了迭代器的

        标准迭代器的实现方法

        关键接口:IEnumerable,Enumerator

        命名空间:using System.Collections;

        可以通过同时继承IEnumerable和Enumerator实现其中的方法

        using System;
        using System.Collections;
        namespace ConsoleApp1
            class CustomList:IEnumerable,IEnumerator
                private int[] list;
                //从-1开始的光标 用于表示 数据移到了哪个位置
                private int position = -1;
                public CustomList()
                    list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
                public object Current
                        return list[position];
                #region IEnumerable
                public IEnumerator GetEnumerator()
                    Reset();
                    return this;
                #endregion
                public bool MoveNext()
                    //移动光标
                    ++position;
                    //是否溢出 溢出就不合法
                    return position < list.Length;
                //areset是重置光标位置 一般写在获取 IEnumeraror对象这个函数中
                //用于第一次重置光标位置
                public void Reset()
                    position = -1;
            class Program
                static void Main(string[] args)
                    //foreach本质
                    //1.先获取in后面这个对象的 IEnumerator
                    //  会调用对象其中的 GetEnumerator 方法 来获取
                    //2.执行得到这个IEnumerator对象中的MoveNext方法
                    //3.只要MoveNext方法的返回值时true,就会得到Current
                    //  然后复制给 item
                    CustomList list = new CustomList();
                    foreach(int item in list)
                        Console.WriteLine(item);
        

        用yield return 语法糖实现迭代器

        yield return 是C#提供给我们的语法糖

        所谓语法糖,也称糖衣语法

        主要作用就是将复杂逻辑简单化,可以增加程序的可读性

        从而减少代码出错的机会

        关键接口:IEnumerable

        命名空间:using System.Collections;

        让想要通过foreach遍历的自定义类实现接口中的方法GetEnumerator即可

      class CustomList2 : IEnumerable
              private int[] list;
              public CustomList2()
                  list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
              public IEnumerator GetEnumerator()
                  for(int i=0;i<list.Length;i++)
                      //yield关键字 配合迭代器使用
                      //可以理解为 暂时返回 保留当前的状态
                      //一会儿还会再回来
                      //C#的语法糖
                      yield return list[i];
      

      用yield return 语法糖为泛型类实现迭代器

      class CustomList<T> : IEnumerable
              private T[] array;
              public CustomList2(params T[] array)
                  this.array = array;
              public IEnumerator GetEnumerator()
                  for(int i=0;i<array.Length;i++)
                      yield return array[i];
      

      var隐式类型

      • var是一种特殊的变量类型
      • 它可以用来表示任意类型的变量
      • 1.var不能作为类的成员 只能用于临时变量申明时
      • 也就是 一般写在函数语句块中
      • 2.var必须初始化
      var i = 5;
      var s = "123";
      var array = new int[] { 1, 2, 3, 4 };
      var list = new List<int>();
      

      设置对象初始值

      • 申明对象时,可以通过直接写大括号的形式初始化公共成员变量和属性
          class Person
              private int money;
              public bool sex;
              public string Name
              public int Age
              public Person(int money)
                  this.money = money;
      
       Person p = new Person(100) { sex = true, Age = 18, Name = "唐老狮" };
       Person p2 = new Person(200) { Age = 18 };
      

      设置集合初始值

      • 申明集合对象时,也可以通过大括号 直接初始化内部属性
      int[] array = new int[]{1,2,3,4,5};
      List<int> listInt = new List<(){1,2,3,4,5,6};
      List<Person> listPerson = new List<Persion>(){
          new Person(200),
          new Person(100){Age = 10},
          new Person(1){sex = true,Name = "唐老狮"},
      Dictionart<int,string> dictionary = new Dictionary<int,string>()
          {1,"123"},{2,"222"}
      
      • var 变量可以申明为自定义的匿名类型
      var v = new{age = 10,money = 11,name = "小明"};
      Console.WriteLine(v.age);
      Console.WriteLine(v.name);
      
      //1.值类型是不能赋值为空的
      //int c = null;  //会报错,值类型不能赋值为 空的
      //2.申明时 在值类型后面加?可以赋值为空
      int? c = null;
      //3.判断是否为空 
      if(c.HasValue)
          //打印值有这两种方法,一样的
          Console.WriteLine(c);
          Console.WriteLine(c.Value);
      //4.安全获取可空类型值
      int? value = null;
      //  4-1.如果为空 默认返回值类型的默认值
      Console.WriteLine(value.GetValueOrDefault()); //输出0
      //  4-2.也可以指定一个默认值
      Console.WriteLine(value.GetValueOrDefault(100)); //输出100,并没有给value赋值
      Console.WriteLine(value); //无输出
      float? f = null;
      double? d = null;
      //引用类型
      object o = null;
      if(o! = null)
         Console.WriteLine(o.ToString());
      //相当于是一种语法糖 能够帮助我们自动去判断o是否为空
      //如果是null就不会执行tostring,也不会报错
      Console.WriteLine(o?.ToString());
      int[] arrayInt = null;
      Console.WriteLine(array?[]); //不会报错 只是返回空而已
      Action action = null;
      if(action != null)
          action();
      action?.Invoke(); //和上面那段相同
      

      空合并操作符

      //空合并操作符??
      //左边值??右边值
      //如果左边值为null 就返回右边值 否则返回左边值
      //只要是可以为null的类型都能用
      //相当于一个三目运算符
      int ? intV = null;
      int intI = intV == null?100 : intV.value;
      intI = intV ?? 100; //和上一句一样
      Console.WriteLine(intI);
      string str = null;
      str = str ?? "hahah"; 
      Console.WriteLine(str);
      

      内插字符串

      • 关键符号:$
      • 用$来构造字符串,让字符串中可以拼接变量
      string name = "唐老狮";
      int age = 18;
      Console.WriteLine($"好好学习,{name},年龄:{age}");
      

      单逻辑简略写法

      //当循环或者if语句中只有一句代码时,可以省略大括号
      if(true) Console.WriteLine("123123");//不用大括号
      for(int i = 0;i < 10; i++)
          Console.WriteLine(i);
      class  Person
          public bool sex;
          public string Name
              //简略写法
              get => "唐老狮";
              set => set = true;
          public int Add(int x,int y) => x+y;
          public void Speak(string str) => Console.WriteLine(str);
      

      值类型和引用类型

      知识回顾:

      • 无符号:byte,ushort,uint,ulong
      • 有符号:sbyte,short,int,long
      • 浮点数:float,double,decimal
      • 特殊:char,bool
      • 枚举:enum
      • 结构体:struct
        • string
        • class
        • interface

        值类型和引用类型的本质区别:

        • 值的具体内容存在栈内存上
        • 引用的具体内容存在堆内存上

        如何判断值类型和引用类型?

        F12进到类型的内部去查看,是class就是引用,是struct就是值。

        命名空间 -> 类、接口、结构体 -> 函数、属性、索引器、运算符重载等 -> 条件分支、循环

        • 上层语句块:类、结构体
        • 中层语句块:函数
        • 底层语句块:条件分支、循环等

        我们的逻辑代码写在哪里?

        • 函数、条件分支、循环-中底层语句块中

        我们的变量可以申明在哪里?

        • 上、中、底都能申明变量
        • 上层语句块中:成员变量
        • 中、底层语句块中:临时变量

        变量的生命周期

        编程时,大部分都是临时变量

        在中底层申明的临时变量(函数、条件分支、循环语句块等)

        语句块执行结束

        没有被记录的对象将被回收或变成垃圾

        值类型:被系统自动回收

        引用类型:栈上用于存地址的房间被系统自动回收,堆中具体内容变成垃圾,待下次GC回收

        int i = 1;
        string str = "123";
        

        想要不被回收或者不变垃圾

        必须将其记录下来

        如何记录?

        在更高层级记录或者使用静态全局变量记录

      int b = 0;
      while(1)
      

      结构体中的值和引用

      结构体本身时值类型

      前提:该结构体没有做为其他类的成员

      在结构体中的值,栈中存储值具体的内容

      在结构体中的引用,堆中存储引用具体的内容

      引用类型始终存储在堆中

      真正通过结构体使用其中引用类型时知识顺藤摸瓜

    struct TestStruct
        public Test t;
        public int i;
    TestStruct td = new TestStrict();
    

    类中的值和引用

    类本身是引用类型

    在类中的值,堆中存储具体的值

    在类中的引用,堆中存储具体的值

    值类型跟大哥走,引用类型一根筋

数组中的存储规则

  • 数组本身是引用类型
  • 值类型数组,堆中房间存具体内容
  • 引用类型数组,堆中房间存地址

结构体继承接口

  • 利用里氏替换原则,用接口容器装载结构体存在装箱拆箱
interface ITest
    int Value
struct TestStruct : ITest
    int value;
    public int Value
            return value;
            this.value = value;
TestStruct obj1 = new TestStruct();
obj1.Value = 1;
Console.WriteLine(obj1.Value);  //1
TestStruct obj2 = obj1;
obj2.Value = 2;
Console.WriteLine(obj1.Value);  //1
Console.WriteLine(obj2.Value);  //2
ITest iObj1 = obj1; //里氏替换原则 父类装子类 装箱 value=1
ITest iObj2 = iObj1;  //没有在堆里新开房间
iObj2.Value = 99;
Console.WriteLine(iObj1.Value);  //99
Console.WriteLine(iObj2.Value);  //99
TestStruct obj3 = (TesrStruct)iObj1; //拆箱
                            
python通过keytab连接hive kerberos python调用hive脚本

Hive的 TRANSFORM 关键字提供了在SQL中调用自写脚本的功能,本实例通过python脚本对电影数据进行清洗,帮助读者了解hive调用python脚本的整个流程。操作步骤:1、创建基表CREATE TABLE u_data ( userid INT, //用户ID movieid INT, //电影ID rating INT, //电影评分

内容概要自增特性约束条件之外键 外键简介外键关系外键SQL语句之一对多关系外键SQL语句之多对多关系外键SQL语句之一对一关系查询关键字 数据准备查询关键字之select与from查询关键字之where筛选查询关键字之group by分组内容详情自增特性create table t1( id int primary key auto_increment, name varchar(32)