一.什么是递归/冒泡
任何一个方法既可以调用其他方法又可以调用自己,而当这个方法调用自己时,我们就叫它递归函数或者递归方法!
通常递归有两个特点:
1.递归方法一直会调用自己直到某些条件满足,也就是说一定要有出口;
2.递归方法会有一些参数,而它会把这些新的参数值传递给自己;(自己调自己);
递归通常用于: ①.阶乘 ②.斐波拉切数列;
1.阶乘
阶乘(!)是小于某个数的所有正整数的乘积;
注意:0既不是正整数,又不是负整数;0是整数;
0!=1 1!=1 2!=2*1!=2 3!=3*2!=6 4!=4*3!=24 5!=5*4!=120 ... n!=n*(n-1)!
pubic long Factorial(int n){ if(n==0){ return 1;
} return n*Factorial(n-1); }
2.(Fibonacci)斐波拉切数列:
Fibonacci数列是按以下顺序排列的数字:
1,1,2,3,5,8,13,21,34,55....
我们不难发现数列的排列规律是:后一个数加上前一个数,以此类推;
如果F0=0并且F1=1那么Fn=F(n-1)+F(n-2);
public long Fabinacci(int n){ if(n==0||n==1){ // 满足条件 return n;
} return Fabinacci(i-2)+Fabinacci(i-2); // 返回值 }
3.冒泡排序是如何实现的?
思路:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。重复第一趟步骤,直至全部排序完成。
int [] array = new int [*] ; int temp = 0 ; for (int i = 0 ; i < array.Length - 1 ; i++) { for (int j = i + 1 ; j < array.Length ; j++) { if (array[j] < array[i]) { temp = array[i] ; array[i] = array[j] ; array[j] = temp ; } } }
二.委托
1.什么是委托?
委托是一种动态调用方法的类型,属于引用型。
委托是对方法的抽象和封装。委托对象实质上代表了方法的引用(即内存地址)
所有的异步都是委托 委托就是函数当入参 委托被各种语法糖遮蔽了 =>就是委托 匿名委托
委托的声明原型是
delegate <函数返回类型> <委托名> (<函数参数>)
例子:public delegate void MyDelegate(int number);//定义了一个委托MyDelegate,它可以注册返回void类型且有一个int作为参数的函数
特点:
- 委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象实例和方法。
- 委托允许将方法作为参数进行传递。
- 委托可用于定义回调方法。
- 委托可以链接在一起;例如,可以对一个事件调用多个方法。
- 委托签名不需要与方法精确匹配。
- C# 2.0 版引入了匿名方法的概念,可以将代码块作为参数(而不是单独定义的方法)进行传递。 C# 3.0 引入了 Lambda 表达式,利用它们可以更简练地编写内联代码块。 匿名方法和 Lambda 表达式(在某些上下文中)都可编译为委托类型。 这些功能现在统称为匿名函数。
应用场景
1.比如跨线程更新winform UI,线程调用处理等等
2.传递方法;把方法包裹起来, 传递逻辑。异步多线程执行
3.委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If … Else(Switch)语句,同时使得程序具有更好的可扩展性。
Lambda表达式实现匿名委托

public partial class WebForm3 : System.Web.UI.Page { public delegate int CalculatorAdd(int x, int y); protected void Page_Load(object sender, EventArgs e) { //方法一: CalculatorAdd cAdd1 = (int x, int y) => { return x + y; }; int result1 = cAdd1(5, 6); //方法二: CalculatorAdd cAdd2 = (x, y) => { return x + y; }; int result2 = cAdd2(5, 6); //方法三: CalculatorAdd cAdd3 = (x, y) => x + y; int result3 = cAdd2(5, 6); } }
2.4大内置委托(比较老的 delegate 委托,新升级4种)
-
Action:可以传入参数,没有返回值的委托
-
static void Main(string[] args) { Action<int, int> action = new Action<int, int>(addVoid); // Action<int, int> 这就是方法的原型 action(1, 2); action.Invoke(2, 3); //基本的使用方法和delege都是差不多的,包括异步的写法也是相同的, //但是他升级了,厉害之处是加入了lamda, Action<int, int>就是声明委托原型 简化了写法,通过lamda (n,m)=>{} 匿名函数的写法 Action<int, int> actionOne = new Action<int, int>((n, m) => { Console.WriteLine("lamda方式1 计算结果{0}", (n + m)); }); actionOne.Invoke(4, 5); //lamda 搞法很优雅 Action<int, int> actionTwo = (n, m) => { Console.WriteLine("lamda方式2 计算结果{0}", (n + m)); }; actionTwo.Invoke(3, 4); Console.ReadKey(); } static void addVoid(int a, int b) { Console.WriteLine("计算结果{0}", (a + b)); }
-
Func: 跟Action 的区别是可以有返回值
-
/// <summary> /// 扩展方法 /// </summary> public static class DelegateExtend { /// <summary> /// 模仿Linq的Where操作 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="scoure">数据源</param> /// <param name="func">委托(自定义bool条件)</param> /// <returns></returns> public static IEnumerable<T> ExtWhere<T>(this IEnumerable<T> scoure, Func<T, bool> func) { //遍历数据源的数据 foreach (var item in scoure) { //请求委托完成条件的筛选返回bool bool bResult = func(item); //把通过筛选提交的数据源,返回出去 if (bResult) { yield return item; } } } } //查询出所有数据 IEnumerable<Student> student = sql.QueryList<Student>(); //定义一个匿名方法,并赋值给委托 Func<Student, bool> func = delegate(Student s) { //自定义代码逻辑,返回bool类型 return s.Id.Equals("1"); }; //传入委托 IEnumerable<Student> list = student.ExtWhere(func); //第二种方法,使用linq语法(自定义逻辑) IEnumerable<Student> list1 = student.ExtWhere(p => p.Id.Equals("1")); 上面就是一个简单但很常见的委托使用场景 从侧面理解一下这段代码, ExtWhere 是我要做的一件事情,但这件事情里面我需要一个bool类型的返回结果,那么我委托func去帮我获取到这个bool类型的结果 我刚开始的时候,对委托的理解觉得很困难,总感觉晕晕的,但是自己没事多练习练习之后,就会很好理解了 上面的demo很好的解释了使用委托的好处 解耦:抽出自定义逻辑,保留相同的逻辑,使代码分离 最大限度的简化代码:解耦的同时,又减少了代码量(自定义逻辑,可以避免相同逻辑的代码重复)
-
Comparison:返回整数,比较两个对象 (应用于集合排序)
-
List<Student> list=new List<Student>(); //Student类中含有Age属性 list.AddRange(....); //添加数据 //以下对Student集合按照其Age属性从小到大排序 list.Sort( (x, y) => { if (x.Age < y.Age) { return -1; } else if (x.Age > y.Age) { return 1; } else return 0; } ); 或者以下更加简单的写法********: list.sort((x,y)=>x.Age.CompareTo(y.Age));
-
Predicate:返回bool ,根据条件筛选(Predicate<T>又是对Func<T, bool>的简化)
-
Predicate<int> myPredicate = i => i > 10;
3.多播委托
实例化委托时必须将一个匹配函数注册到委托上来实例化一个委托对象,但是一个实例化委托不仅可以注册一个函数还可以注册多个函数,注册多个函数后,在执行委托的时候会根据注册函数的注册先后顺序依次执行每一个注册函数。
函数注册委托的原型:
<委托类型> <实例化名>+=new <委托类型>(<注册函数>)
例如:MyDelegate _myDelegate+=new MyDelegate(CheckMod);//将函数CheckMod注册到委托实例_checkDelegate上
在.net 2.0开始可以直接将匹配的函数注册到实例化委托:
<委托类型> <实例化名>+=<注册函数>
例如:MyDelegate _myDelegate+=CheckMod;//将函数CheckMod注册到委托实例_myDelegate上
注意:委托必须先实例化以后,才能使用+=注册其他方法。如果对注册了函数的委托实例从新使用=号赋值,相当于是重新实例化了委托,之前在上面注册的函数和委托实例之间也不再产生任何关系。
有+=注册函数到委托,也有-=解除注册
例如:MyDelegate _myDelegate-=CheckMod;
多播委托可以带返回值,但是只有最后一个方法的返回值会被返回。如果在委托注册了多个函数后,如果委托有返回值,那么调用委托时,返回的将是最后一个注册函数的返回值。
Action doSome = new Action(DoSome); doSome += new Action(DoSome); doSome += DoSome; doSome();//按顺序执行,最后结果是执行3次DoSome方法 doSome -= DoSome;//减少一次DoSome执行 doSome();//按顺序执行,最后结果是执行2次DoSome方法 多播委托,按顺序执行,多播委托,用Action, Func带返回值的只执行完后,只得到最后一个结果,所以没有意义。
4.泛型委托
泛型委托包括Action、Func和Predicate三种委托。
1.Action-无返回值

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DelegateSamples { class Program { static void Main(string[] args) { /* Action<T>:封装只有一个参数(类型为T),不包括返回值的签名函数,它包括以下几种情况: * Action<T>、Action<T1,T2>、Action<T1,T2,T3>、Action<T1,T2,T3,T4> * 声明: * delegate void Action(); * delegate void Action<T1>(T1 arg1); * delegate void Action<T1,T2>(T1 arg1,T2 arg2); * delegate void Action<T1,T2,T3>(T1 arg1,T2 arg2,T3 arg3); * delegate void Action<T1,T2,T3,T4>(T1 arg1,T2 arg2,T3 arg3,T4 arg4); */ Action<string> action = SpeakEnglish; action("KoalaStudio"); Action<string, string> action2 = SpeakTwoLanguage; action2("KoalaStudio","Koala工作室"); Console.ReadKey(); } private void SpeakChinese(string msg) { Console.WriteLine("你好,我是{0}",msg); } private static void SpeakEnglish(string msg) { Console.WriteLine("Hello,I'm {0}",msg); } private static void SpeakTwoLanguage(string msg1, string msg2) { Console.WriteLine("你好,我是{0}",msg1); Console.WriteLine("Hello,I'm {0}",msg2); } } }
2.Func-有返回值

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DelegateSamples { class Program { static void Main(string[] args) { /* Func<T,TResult>:封装一个具有参数(类型为T),返回TResult类型值的签名函数,它包括以下几种情况: * Func<T,TResult>、Func<T1,T2,TResult>、Func<T1,T2,T3,TResult>、Func<T1,T2,T3,T4,TResult> * 声明: * ……略去 */ Func<string,string/*这是返回值类型*/> func = SpeakEnglish; func("KoalaStudio"); Func<string, string, string/*这是返回值类型*/> func2 = SpeakTwoLanguage; func2("KoalaStudio","Koala工作室"); Console.ReadKey(); } private static string SpeakEnglish(string msg) { return string.Format("Hello,I'm {0}", msg); } private static string SpeakTwoLanguage(string msg1, string msg2) { Console.WriteLine("你好,我是{0}",msg1); Console.WriteLine("Hello,I'm {0}",msg2); return string.Format("你好,我是{0};Hello,I'm {1}", msg1,msg2); } } }
3.Predicate

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DelegateSamples { class Program { static void Main(string[] args) { /* bool Predicate<T>:表示定义一组条件并确定指定对象是否符合这些条件的方法。 * 通常,此类委托由Array和List类的几种方法使用,用于在集合中搜索元素。 * delegate bool Predicate<T>(T obj),如果obj符合此委托表示的方法中定义的条件,则返回true,否则返回false */ List<string> listString = new List<string>() { "a","abc","koala","xyz","take" }; //List对象的FindAll定义:public List<T> FindAll(Predicate<T> match); //match 类型:System.Predicate<T> 委托,用于定义要搜索的元素应满足的条件。 //返回值 //类型:System.Collections.Generic.List<T> //如果找到,则为一个 List<T>,其中包含与指定谓词所定义的条件相匹配的所有元素;否则为一个空 List<T>。 Predicate<String> match = delegate(string word) { if (word.Length > 4) { return true; } return false; }; List<string> result = listString.FindAll(match); } } }
三.事件(Event)
1.什么是事件
谈到委托,必提事件,事件本质是对委托的封装,对外提供add_EventName(对应+=)和remove_EventName(对应-=)访问,从而实现类的封装性。
基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
C# 中使用事件机制实现线程间的通信。
+=就是發生新事件的同時通知你;
-=就是發生新事件的同時不通知你;
事件是以委托为基础。委托是调用回调方法的一种类型安全的方式。对象凭借回调方法接收他们订阅的通知
回调机制的应用非常多,例如控件事件、异步操作完成通知等等;.net 通过委托来实现回调函数机制。相比其他平台的回调机制,委托提供了更多的功能,例如它确保回调方法是类型安全的,支持顺序调用多个方法,以及调用静态方法和实例方法。
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
事件能解决什么问题
将公有的委托变量定义为私有变量,从而满足类的封装性原则;
具有委托具有的作用;
怎么使用事件
声明委托
声明事件
事件注册方法
事件机制
事件的本质就是委托,向外提供两个访问方法add_EventName(对应+=)和remove-EventName(对应-=),我们通过.NET Reflector反汇编工具来查看,到底是不是这样的。
2.声明事件(Event)
在类的内部声明事件,首先必须声明该事件的委托类型。例如:
/// <summary> /// 发生连接事件时的委托 /// </summary> /// <param name="sender"></param> /// <param name="socket">连接的socket</param> public delegate bool OnConnectHandler(object sender, System.Net.Sockets.Socket socket); /// <summary> /// 发生连接断开事件时的委托 /// </summary> /// <param name="sender"></param> /// <param name="socket">连接的socket</param> public delegate void OnDisConnectHandler(object sender, System.Net.Sockets.Socket socket); /// <summary> /// 基本的收到信号事件委托 /// </summary> /// <typeparam name="T">数据类型</typeparam> /// <param name="socket">接收到数据的具体连接</param> /// <param name="obj">数据内容</param> public delegate void OnReceiveHandler<T>(T obj); /// <summary> /// 发生异常的事件委托 /// </summary> /// <typeparam name="T">异常类型</typeparam> /// <param name="exception">异常信息</param> public delegate void OnExceptionHandler<T>(object sender, T exception);
然后,声明事件本身,使用 event 关键字:
/// <summary> /// 连接到服务器的通知 /// </summary> public event OnConnectHandler OnConnect = null; /// <summary> /// 连接断开的通知 /// </summary> public event OnDisConnectHandler OnDisConnect = null; /// <summary> /// 接收到数据的通知 /// </summary> public event OnReceiveHandler<NetData> OnReceive = null; /// <summary> /// 异常通知 /// </summary> public event OnExceptionHandler<Exception> OnException = null;
上面的代码定义了一个名为 OnReceiveHandler的委托和一个名为 OnReceive 的事件,该事件在生成的时候会调用委托。
3.事件和委托的联系与区别
从事件的声明,我们可以大致看出事件与委托的关系,事件是委托的特殊实现,事件是建立在对委托的语言支持之上的。
委托是一种类型,事件是委托类型的一个实例,加上了event的权限控制,限制权限,只允许在事件声明类里面去invoke和赋值,不允许外面,甚至子类调用。
/// <summary> /// 接收网络数据 /// </summary> private SmTcpClient mTcpRecv = null; mTcpRecv = new SmTcpClient(Config.CenterIp, Config.CenterPort, true); mTcpRecv.OnReceive += MTcpRecv_OnReceive; mTcpRecv.OnException += MTcpRecv_OnException; mTcpRecv.OnDisConnect += MTcpRecv_OnDisConnect; mTcpRecv.OnConnect += MTcpRecv_OnConnect; mTcpRecv.Start();
Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型。这个是祖宗。
Func可以接受0个至16个传入参数,必须具有返回值。
Action可以接受0个至16个传入参数,无返回值。
Predicate只能接受一个传入参数,返回值为bool类型。
四.链表=>二叉树=>平衡二叉树=>红黑树=>B-Tree=>B+Tree
1.链表
链表结构是由许多节点构成的,每个节点都包含两部分:
- 数据部分:保存该节点的实际数据。
- 地址部分:保存的是下一个节点的地址。
链表的特点:
- 结点在存储器中的位置是任意的,即逻辑上相邻的数 据元素在物理上不一定相邻
- 访问时只能通过头指针进入链表,并通过每个结点的 指针域向后扫描其余结点,所以寻找第一个结点和最后一 个结点所花费的时间不等
链表的优点:
- 数据元素的个数可以自由扩充 、插入、删除等操作不必移动数据,只需 修改链接指针,修改效率较
链表的缺点:
- 存储密度小 、存取效率不高,必须采用顺序存取,即存 取数据元素时,只能按链表的顺序进行访问
2.二叉树
由一个根结点及两棵互不相交的、分别称作这个根的左子树和右子树的二叉树组成。这个定义是递归的。由于左、右子树也是二叉树, 因此子树也可为空树。
二叉树在很大程度上解决了这个缺点,二叉树是按值来保存元素,也按值来访问元素。怎么做到呢,和链表一样,二叉树也是由一个个节点组成,不同的是链表用指针将一个个节点串接起来,形成一个链,如果将这个链“拉直”,就像平面中的一条线,是一维的。而二叉树由根节点开始发散,指针分别指向左右两个子节点,像树一样在平面上扩散,是二维的。
二叉排序树是一种比较有用的折衷方案。
数组的搜索比较方便,可以直接用下标,但删除或者插入某些元素就比较麻烦。
链表与之相反,删除和插入元素很快,但查找很慢。
二叉排序树就既有链表的好处,也有数组的好处。
在处理大批量的动态的数据是比较有用。
3.平衡二叉树
平衡二叉树追求绝对平衡
平衡二叉树的目的是为了减少二叉查找树层次,提高查找速度
4.红黑树
红黑树放弃了追求完全平衡,追求大致平衡
红黑树是二叉树的进化体也可以说是平衡二叉树
5.B-Tree
是一种多路搜索树
6.B+Tree
是大多数 MySQL 存储引擎的默认索引类型。因为不再需要进行全表扫描,只需要对树进行搜索即可,所以查找速度快很多。
因为 B+ Tree 的有序性,所以除了用于查找,还可以用于排序和分组。
B+ 树是一种树数据结构,是一个n叉树,每个节点通常有多个孩子,一棵B+树包含根节点、内部节点和叶子节点。根节点可能是一个叶子节点,也可能是一个包含两个或两个以上孩子节点的节点。
B+树是应文件系统所需而出的一种B-树的变型树 更适合文件索引系统 在B-树基础上,为叶子结点增加链表指针
7.索引
红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 作为索引结构,这是因为使用 B+ 树访问磁盘数据有更高的性能。
五.表达式树
这里先讲解下表达式和表达式树,表达式相信大家都知道,比如x+5或者5,都可以算是表达式,而表达式树里面的树指的二叉树,也就是表达式的集合,C#中的Expression类就是表达式类。对于一棵表达式树,其叶子节点都是参数或者常数,非叶子节点都是运算符或者控制符。
在.Net 里面的Linq to SQL就是对表达式树的解析
C# 编译器只能从表达式 Lambda(或单行 Lambda)生成表达式树
Expression<Func<int, bool>> lambda = num => num < 5;
在表达式创建那,我们组合创建了一个Lambda表达式,那么应该怎么使用它呢?在“表达式的解析”里面,LambdaExpression类和Expression<TDelegate>类都有一个Compile的方法,学名是Lambda表达式的委托,其实就是Lambda表达式编译函数的委托,所以我们只需要调用他,得到的结果就是一个函数方法。