目录
数据结构
集合
动态数组ArrayList
习题:声明一个Monster类,有一个Attack方法,用一个ArrayList去封装Monster的对象,装10个,遍历monster的list让他们释放攻击方法
哈希表HashTable
创建一个武器类,有一个属性叫做id,每个武器对象的id不一样,创建10把武器,把这10把武器通过id,存储在哈希表中,可以通过id在哈希表中找到对应的武器
栈Stack
栈在游戏开发中的应用
队列Queue
游戏开发中的应用
泛型
手动输入一组数列(可任意长度),并对这个数列进行排序,每次提示用户输入:1、添加一个数字;2、排序并显示数列
书写一个怪物(Monster)类,在怪物类的构造中将其添加到一个静态列表,以怪物类做为基类派生Boss和Gablin的对象产生不用的攻击行为(多态),可以写一个方法,让怪物按攻击力进行排序从小到大。
字典
书写一个方法,可以把输入的数字转换成中文数字
委托
老师们会在下课时打铃(事件) 学生们想在打铃事件发生的时候做自己的事情;小明想在打铃的时候去买东西吃;小张想在打铃时去打水;小红想在打铃时去打羽毛球
事件
观察者模式
匿名委托与Lambda表达式
有一个int类型的List,升序排序和降序排序,不能使用List的Sort方法,只能自己写一个排序的方法,通过委托传递方法变量去改变排序的逻辑
匿名方法
Lambda表达式 匿名方法的升级
泛型委托
Action的演示
Func的演示
描述数据之间的关系
行为:添加数据、删除数据、插入数据、查找数据
追加数据:向结构的末尾添加一个数据
删除数据:向结构中删除指定的数据
插入数据:向结构中某位置插入指定的数据
查找数据:可以查找并访问到该数据
修改数据:对该结构指定的数据进行重新赋值
线性、链式、树状、图形、散列
链式,是非连续的内存空间,是每个数据分成三部分
头、数据、尾,每个数据的尾部连接下一个数据的头部
所以在内存不是连续的空间,而是一个一个的空间,通过头尾地址连接在一起
Collection是C#写好的数据结构类库
ArrayList、HashTable、Stack、Queue
如果你是用这些数据结构类的模板,要先引用System.Collections;
就可以通过类名去实例化它的对象
是封装过后的数组,里面的元素容器为Object类型
这样ArrayList就适用于所有的数据类型
因为Object类型是所有类的父类,又因为里氏转化原则,父类可以装载子类,所以ArrayList可以装载所有数据类型
属性:
Count:记录当前拥有多少个元素
Capacity:记录当前可以包含多少元素
方法:
添加.Add(Object value) 把当前这个对象添加到数组中
删除 .Remove(Object value) 查询此元素,并移除第一个匹配的元素项
.RemoveAt(int index) 根据下标号移除该元素
插入 .Insert(int index ,Object value) 把对应对象插入到对应的下标
访问/修改:通过索引器下标号
排序:Sort();
反转:Reverse();
检测是否包含:Contains(Object value)
检测该集合是否包含改元素,如果包含返回true,不包含返回false
查找索引.IndexOf(Object value)
找到第一个匹配该元素的下标号并返回
如果没找到,则返回-1
internal class Monster
{
public string name;
public Monster(string name)
{
this.name = name;
}
public void Attack()
{
Console.WriteLine("{0}攻击了", name);
}
}
static void Main(string[] args)
{
ArrayList monsterList=new ArrayList();
for(int i=0;i<10;i++)
{
monsterList.Add(new Monster("第"+i+"号哥布林"));
}
for (int i = 0; i < monsterList.Count; i++)
{
if(monsterList[i] is Monster)
{
(monsterList[i] as Monster).Attack();
//ArrayList装载的是Object类型,需要里氏转换原则
}
}
}
也是System.Collections集合下的数据结构类
它储存的也是object类型的对象,但是它在内存中是散列排布的
因为这个特性,非常适合存储大量的数据
在HashTable中一个键只能对应一个值,一个值可以对应多个键,多对一的关系
HashTable存储的是<键,值>对
HashTable table=new HashTable();
属性:
Count:HashTable包含的键值对的数目
Keys:HashTable中键的集合
Values:HashTable中值的集合
方法:
增删改查
Add(key,value)在哈希表中添加一对键值对
Remove(key)删除键值
因为一个值有可能对应多个键,这样就不能把整个键值对删除掉
只要没有键指向这个值,就会自动被释放掉,所以只需要删除键值就可以了
Contains(key)检测是否包含此键值对
ContainsKey(key)检测是否包含此键
ContainsValue(value)检测是否包含这个值
访问,索引器[键]
在内存中散乱排布,用foreach去遍历键
foreach(var key in table.keys)
{
Console.WriteLine(hashtable[key]);
}
static void Main(string[] args)
{
Hashtable table=new Hashtable();
//假设有一把武器,叫霜之哀伤,id为123
//并把霜之哀伤放进我的武器目录里
Weapon a = new Weapon("霜之哀伤");
table.Add("123",a);
table.Add("456",a);
Console.WriteLine(table["456"]);
}
internal class Weapon
{
public string name;
public Weapon(string name)
{
this.name = name;
}
public override string ToString()
{
return name;
}
}
也是System.Collections下的数据结构类,存储的依然是Object 类型的对象
Stack stack=new Stack();
Count:实际拥有的元素个数
栈的释放顺序是先进后出
压栈——Push(Object 对象)把这个对象添加到栈的顶部
弹栈——Pop()把栈顶的元素弹出来,会删除
Peek() 返回栈顶的元素,不删除
在遍历弹栈的时候要注意,pop方法会删除你的对象,导致Count属性发生改变,
所以,应该用一个变量存储一下一开始的Count值,根据这个变量,来弹栈就可以把栈中所有的数据弹出去
Stack stack = new Stack();
for(int i=0;i<10;i++)
{
stack.Push(i);
}
int static_Count=stack.Count;
for(int i=0;i
{
Console.WriteLine(stack.Pop());
}
用来实现”返回/撤销”功能。可以将每一步的操作或者是坐标信息加入到stack中,当玩家按下返回/撤销键时将上一步操作从栈中移除并根据其中的信息在游戏中反向操作
常常被用到在游戏”状态/窗口/场景”管理中。在游戏中,往往会有多个窗口叠加在一起,或者从一个场景进入到子场景。这时,通过在进入“新场景/打开新窗口”时,将这个”场景/窗口”加入到stack中,玩家选择返回时再从栈中弹出并销毁,所有的事件和操作都只对当前的顶端的窗口生效
是System.Collections下的数据结构类,存储Object类型的对象
Queue queue=new Queue();
队列的释放顺序:先进后出
属性
Count:该结构包含的元素个数
方法:
EnQueue(Object value)进入队列的末尾处
DeQueue() 返回并移除队列最前面的那个元素
Peek() 队列中队首的元素返回,但不删除
回合制游戏中的行动顺序队列(Turn Queue)
回合制和半回合制游戏中常常有速度、行动力的概念来决定场上所有单位的行动顺序,这个时候可以通过队列来安排。当然,很多游戏中会有提升或降低速度的技能和物品,这个时候会需要重新生成队列。管理经营类游戏中的生产队列
很多管理经营类游戏,或者即时战略游戏中都会有生产队列的概念。通过使用队列来提前规划之后的生产顺序可以使玩家操作起来更为方便。一个典型的例子就是文明系列中在城市里的生产列队,提前安排之后需要生成的单位或设施,将后续需要制造的东西依次加入队列,当当前生产任务完成时,从队列中移除并获取下一个需要生成的单位。行动队列
很多及时战略游戏和MOBA类游戏中都有用队列提前安排之后的行动的功能。例如DotA中可以在TP的时候按住Shift将跳刀加到行动队列中实现落地瞬间跳走,沙王施法前摇时将跳到放到队列中实现跳大等操作。剧情对话
当剧情对话会因为玩家的选择产生分支的时候,常常需要用树结构来储存,但归根结底,这可以被当作一种非线性的队列。实际运行的时候,还是可以用Queue来储存和操作正在播放的剧情文字,分支产生并被选择以后再将该分支下的对话加入到队列中。动画播放,多节点移动
游戏动画中,有时候会有沿着一系列节点移动的操作,这个过程可以使用队列来完成,将所有节点按照顺序加入队列,然后用dequeue获取并移除队列顶端的点来实现按照相同顺序移动。消息、事件的传输和分发
一些网游或者多进程的单机需要用接收、处理从网络或其他进程传入的指令和消息,而当本地在处理消息的时候,其他陆续传入的消息将在队列中等待,然后当前一个任务执行完毕后从队列中获取下一个需要被执行的指令或需要处理的消息。
因为在编程中想先不定义数据类型,只想先写逻辑,可以使用Object类型,这样逻辑就适用于所有类型,但是,在运行中,Object类型的变量会需要转换到对应类型,浪费资源,所以出现泛型,代替Object类型的方案
使用泛型,可以延迟定义数据类型,来编写程序
泛型是一种将逻辑应用到不同数据类型上的机制,可以通过类型代替符来暂时代参数的数据类型,这样只需要在编译的时候,编译器会自动将替代符编译成对应数据类型来处理
泛型方法
定义泛型方法
访问修饰符 返回类型 方法名
(T 参数,U参数){} 可以在方法名后使用<类型替代符>来定义一个泛型方法
static void Main(string[] args)
{
List list = new List();
list.Add(1);
list.Add(4);
list.Add(8);
list.Add(2);
list.Add(3);
list.Add(9);
list.Add(5);
list.Add(6);
list.Add(7);
list.Add(10);
Console.WriteLine("提示:用户输入1,可添加一个数字;如果用户输入2,排序并显示此数列");
int a=int.Parse(Console.ReadLine());
if(a==1)
{
list.Add(a);
}
if(a==2)
{
list.Sort();
for(int i=0;i
static void Main(string[] args)
{
for(int i = 0; i < 10; i++)
{
new Goblin("第" + i + "号哥布林");
}
new Boss("拉格拉罗斯");
//Console.WriteLine(MonsterManager.monsterList[1]);
for (int i = 0; i < MonsterManager.monsterList.Count; i++)
{
MonsterManager.monsterList[i].Attack();
}
}
internal class MonsterManager
{
public static List
monsterList = new List (); public static Random r=new Random();
}
class Monster
{
public string name;
public int attack;
public Monster (string name)
{
this.name = name;
MonsterManager.monsterList.Add(this);
}
public virtual void Attack()
{ }
public override string ToString()
{
return String.Format("{0}/攻击力,{i}",name,attack);
}
}
class Goblin : Monster
{
public Goblin(string name) : base(name)
//先去执行base指向的public Monster (string name),
//已经把name的赋值了,再执行自己的攻击
{
attack =MonsterManager.r.Next(50,100);
}
//用overrider关键字重写Attack()方法
public override void Attack()
{
Console.WriteLine("{0}丢了一块石头,砸人特别疼",name);
}
}
class Boss:Monster
{
public Boss(string name):base(name)
{
attack=MonsterManager.r.Next(200,500);
}
public override void Attack()
{
Console.WriteLine("{0}一口炎爆术喷了出来,特别吓人", name);
}
}
把123转换为:壹贰参. Dictionary
思路:建立一个0-9的字典,作为key存储,对应value壹贰参,输入参数作为键在字典里查找,再把值保存下来
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("请输入一个数字");
string input=Console.ReadLine();
UpperNum(input);
}
public static void UpperNum(string input)
{
Dictionary
dic = new Dictionary (); dic.Add("1", "壹");
dic.Add("2", "贰");
dic["3"] = "叁";
dic["4"] = "肆";
dic.Add("5", "伍");
dic.Add("6", "陆");
dic["7"] = "柒";
dic["8"] = "捌";
dic["9"] = "镹";
dic["0"] = "零";
string result = "";
for(int i = 0; i < input.Length; i++)
{
result = result+dic[input[i].ToString()];
}
Console.WriteLine(result);
}
}
是方法的载体(引用),可以承载(一个或多个)方法
是一种特殊数据类型,专门用来存储方法
所有的委托都派生自System.Delegate类
委托可以让我们把方法当作变量去使用
解决了很多代码冗余的问题,也解决了方法回调的问题
委托的定义
访问修饰符delegate返回类型 委托类型名(参数列表)
委托的变量
委托类型名 委托变量名;
委托类型名 委托变量名=new 委托类型(返回类型与参数列表一致方法)
委托的赋值
在装载方法的时候,不用写小括号
委托变量名=返回类型与参数列表一致方法
委托的调用
委托变量名(参数)
委托变量名.Invoke(参数);
列表的查找速度比链表要快
链表的修改速度要比列表快
回调
把委托变量传入方法中去调用
委托的本质
就是方法引用的队列,先进先出,一旦调用会把队列中所有的方法执行完
委托的注册
委托名+=方法名
就可以将多个方法注册进委托变量中
委托的注销
委托名-=方法名
可以将方法从委托列表中移除
委托变量一旦重新赋值,以前引用的方法全部丢失
可以使用委托变量=null全部清空方法列表
static void Main(string[] args)
{
Teacher t = new Teacher("王老师");
Student xiaoming = new Student("小明", "买东西吃");
Student xiaohua = new Student("小花", "打羽毛球");
t.RegisterCallEvent(xiaoming.DoThing);
t.RegisterCallEvent(xiaohua.DoThing);
t.Call();
}
delegate void CallDelegate();
internal class Teacher
{
public string name;
CallDelegate callDel;
public void RegisterCallEvent(CallDelegate del)
{
callDel += del;
}
public void LogoutCallEvent(CallDelegate del)
{
callDel -= del;
}
public Teacher(string name)
{
this.name = name;
}
public void Call()
{
Console.WriteLine("{0}打铃了", name);
if(callDel != null)
{
callDel();
}
}
}
internal class Student
{
public string name;
public string thing;
public Student(string name,string thing)
{
this.name = name;
this.thing = thing;
}
public void DoThing()
{
Console.WriteLine(name+thing);
}
}
委托变量如果公开出去,很不安全,外部可以随意调用
所以取消public,封装它,可以自己书写两个方法,供外部注册与注销,委托调用在子方法里调用,这样封装委托变量可以使它更安全,这个叫做事件。
特性:
- 外部不能随意调用,只能注册和注销
- 只能自己去调用自己的委托
C#为了方便封装委托变量,推出一个特性event事件,在委托变量前用event修饰这个变量,这个委托变量就变成了事件,这样的话,这个委托变量就算公开出去也没有关系,因为外部只能对这个变量进行注册和注销,只能内部进行触发。
模式——视图
发布——订阅
源——收听者
一系列对象来监听另一个对象的行为,被监听者一旦触发事件/发布消息,则被所有监听者收到,然后执行自己的行为
就是使用委托/事件,让一系列对象把它们的行为来注册到我们的委托中
一群对象在观察另外一个对象的行为,当这个对象的行为达成一定条件,则触发了一群对象的反应,要做到以上功能,要搭配事件使用
把一群对象的反应行为注册到被观察的对象的事件中去
delegate bool SortDel(int a,int b);
internal class Program
{
static void Main(string[] args)
{
List
list = new List (); Random r = new Random();
for(int i = 0; i < 10; i++)
{
list.Add(r.Next(1,1000));
}
//匿名方法Sort(list,delegate(int a,int b) { return a>b;});
Sort(list, (a, b) => a < b);//lambad表达式
for(int i = 0; i < list.Count; i++)
{
Console.WriteLine(list[i]);
}
}
public static void Sort(List
list,SortDel del) {
for(int i=0; i < list.Count; i++)
{
for(int j=0; j < list.Count-1-i; j++)
{
if (del(list[j],list[j+1]))
{
int temp=list[j];
list[j]=list[j+1];
list[j+1]=temp;
}
}
}
}
}
和委托搭配使用,方便我们快速对委托进行传参【作用】
不需要我们去定义一个新的函数
直接用delegate关键字代替方法名,后面跟上参数列表与方法体
Delegate(参数列表){方法体}
更加简写
(参数列表)=>{方法体}
当你的方法体只有一条语句的时候,可以不写return,甚至可以没有花括号
参数列表的参数甚至可以不写数据类型
如果说方法体里一旦出现了return,一定要加上花括号
自定义泛型委托
delegate T 委托名
C# 提供好了两个泛型委托的模板供我们使用
这两个模板基本上就可以适用于所有的委托
所以其实是不需要我们自定义的
static void Main(string[] args)
{
Test(100, ActionEvent);
}
public static void ActionEvent(int a)
{
Console.WriteLine(a);
}
public static void Test(int num,Action
> del) {
del(num);
}
static void Main(string[] args)
{
List
list = new List (); Random r = new Random();
for(int i=0;i<10;i++)
{
list.Add(r.Next(1,100));
}
Sort(list,delegate(int a,int b) { return a>b;});
for(int i=0;i
{
Console.WriteLine(list[i]);
}
}
public static void Sort(List
list,Func func) {
for(int i=0;i
{
for(int j=0;j
{
if(func(list[j],list[j+1]))
{
int temp=list[j];
list[j]=list[j+1];
list[j+1]=temp;
}
}
}
}