一.复杂数据结构
1.1枚举
基本概念:被命名的整型常量的集合
声明枚举和声明枚举变量:前者是创建自定义的枚举类型,后者是使用自定义的枚举类型创建枚举变量。
作用:用于表示状态,类型等
声明枚举:
enum E_自定义枚举名//以E开头区分枚举
{
自定义枚举项名字,//不赋值就默认为0,后面依次累加;如果已赋值,则以后就在此基础上累加
自定义枚举项名字=100,
自定义枚举项名字,//默认101
}
声明枚举的位置:在命名空间namespace语句块中声明,以及在class和struct语句块中,不能在函数语句块声明
实例:
enum E_MonsterType
{
Normol,
boss,
}
使用方法:
与普通的类型使用方法一致
(1)E_MonsterType PlayerType=E_MonsterType.boss;
(2)if(playerType==E_MonsterType.boss)
{
console.WriteLine("是个Boss");
}
(3)一般枚举用来配合条件分支语句
switch(monsterType)
{
case E_MonsterType.boss:
break;
case E_MonsterType.Normol:
break;
}
枚举的类型转换:
(1)枚举和int转换
int i=(int)playerType;
console.WriteLine(i);//打印出值
(2) 枚举转string
string str=player.Tostring();
console.WriteLine(i);//打印出枚举项的名字
(3)string转成枚举
//Parse后第一个参数:你要转换为哪个枚举类型 第二个参数:用于转换为相应枚举项的字符串
//转换完毕后,是一个通用类型,需要用括号强转成我们想要的目标
playerType=(E_PlayerType)Enum.parse(typeof(E_PlayerType),"boss")
(4)整型变量强转成枚举
E_PlayerType Player=(E_PlayerType)type;//这里的Type是枚举
枚举作用:
对于游戏开发中,对象有很多状态,比如玩家有一个动作状态,需要一个变量或标识来表示
当前玩家处于哪种状态
综合考虑可能会使用int来表示他的状态:1 行走 2 待机 3 跑步 4 跳跃。。。。。
枚举可以帮我们清晰的分清楚状态的含义。
1.2数组
一维数组:
基本概念:数组是存储一组相同数据类型的集合,分为一维,多维,交错数组,一般情况下 一维数组简称数组
数组声明:
变量类型[] 数组名;//只是申明了数组,但未分配内存
int [] arr1;
变量类型[] 数组名=new 变量类型[数组长度];
int[]arr2=new int [5];//开辟了内存空间,对于int 默认放0
变量类型[] 数组名=new 变量类型[数组长度]{内容1,内容2,内容3.....};
int [] arr3=new int [5]{1,1,1,1,1};
变量类型[] 数组名=new 变量类型[]{内容1,内容2,内容3.....};
int[] arr4 =new int []{1,23,45,6,643};//根据括号里的数字确认长度。
变量类型[] 数组名={内容1,内容2,内容3.....};
int [] arr5={1,2,3,4,5,6};//后面内容决定长度
数组使用:
int[] array={1,2,3,4,5};
(1)数组长度
Console.WriteLine(array.Length);
(2)获取数组元素(都是从0开始)
Console.WriteLine(array[0]);
Console.WriteLine(array[2]);
注意:访问时不能越界(下标最大Length-1)
(3)修改数组中的元素
array[0]=99;//直接赋值,统一类型的值
(4)遍历数组
for(int i=0;i<array.Length;i++)
{
Console.WriteLine(array[i]);
}
(5)增加数组元素
//数组初始化以后不能直接添加新的元素的
//先开新内存,再搬家,然后将原来的数组指向新的内存地址
int [] array2=new int [6];
for(int i=0;i<array.Length;i++)
{
array2[i]=array[i];
}
array=array2; //表示原来的指针指向现在的内存空间,指针重新指向后,原来的就变成垃圾了,C#会自动回收
for(int i=0;i<array.Length;i++)
{
Console.WriteLine(array[i]);//这时就会是6个元素,原来只有5个
}
(6)删除数组的元素
int array3=new int [5];
for(int i=0;i<array3.Length;i++)
{
array3[i]=array[i];//保留前5个元素
}
array=array3;//转换指针
(7)查找元素
//查找元素位置
//遍历每个数组元素是否等于查找的数,然后输出位置
总结:声明,遍历,增删查改,所有的变量类型都可以声明为数组,它是用来批量存储游戏中的同一类型对象的容器 比如:所有的怪物,创建一个就在数组里面
1.3二维数组
基本概念:使用两个下标(索引)来确定元素的数组
下标理解为行标和列表,每行放完,放列
二维数组的声明:
变量类型[,]数组名;//在后面初始化
int[,]arr;
变量类型[,]数组名=new 变量类型[行,列];
int [,]arr2=new int[3,3];
变量类型[,]数组名=new 变量类型[行,列]{{0行内容1,0行内容2,0行内容3...},{1行内容1,1行内容2,1行内容3...}...};
int [,]arr3=new int[3,3]{{1,2,3},
{1,2,3,}
{1,2,3}};
变量类型[,]数组名=new 变量类型[行,列]{{0行内容1,0行内容2,0行内容3...},{1行内容1,1行内容2,1行内容3...}...};
int [,]arr4=new int[,]{{1,2,3},
{4,5,6},
{7,8,9}};
变量类型[,]数组名={{0行内容1,0行内容2,0行内容3...},{1行内容1,1行内容2,1行内容3...}...};
int [,]arr4={{1,2,3},
{4,5,6},
{7,8,9}};
二维数组使用:
int[,]array=new int[,]{{1,2,3},{4,5,6}};
(1)二维数组的长度
得行:Console.WriteLine(array.GetLength(0));
得列:Console.WriteLine(array.GetLength(1));
(2)获取元素
注意第一个元素索引是0,最后一个是长度-1;
Console.WriteLine(array[0,1]);
Console.WriteLine(array[1,2]);
(3)修改二维数组中得元素
array[0,0]=99;
Console.WriteLine(array[0,0]);
(4)遍历二维数组
for(int i=0;i<array.GetLength(0);i++)
{
for(int j=0;j<array.GetLength(1);j++)
{
Console.WriteLine(array[i,j]);
}
}
(5)增加数组元素
数组声明过后,不能在原有得基础上进行添加或者删除
目标两行三列变成三行三列
int[,]array2=new int[3,3];
for(int i=0;i<array.GetLength(0);i++)
{
for(int j=0;j<array.GetLength(1);j++)
{
array2[i,j]=array[i,j];
}
}
array=array2;
array[2,0]=7;//其余多的可以单独赋值
array[2,1]=8;
array[2,2]=9;
删除,查找都可参考一维数组
总结:同一变量类型的行列数据集合,一定掌握的内容:申明,遍历,增删查改
游戏中一般用来存储矩阵或者在控制台小游戏中可以用二维数组来表示地图格子。
1.3交错数组
基本概念:是数组的数组,每个维度的数量可以不同
声明:
变量类型[][]交错数组名;
int[][]arr1;
变量类型[][]arr2=new int[3][];
int[][]arr2=new int [3][];
变量类型[][]交错数组名=new 变量类型[行数][]{一维数组1,一维数组2,一维数组3}
int[][]arr3=new int[3][]{new int[]{1,2,3},
new int[]{1,2},
new int[]{1} };
变量类型[][]交错数组名=new 变量类型[][]{一维数组1,一维数组2,一维数组3}
int[][]arr3=new int[][]{new int[]{1,2,3},
new int[]{1,2},
new int[]{1} };
变量类型[][]交错数组名={一维数组1,一维数组2,一维数组3}
int[][]arr3={new int[]{1,2,3},
new int[]{1,2},
new int[]{1} };
数组的使用:
(1)数组的长度
//行
Console.WriteLine(array.GetLength(0));
//得某一行得列数
Console.WriteLine(array[0].Length);
(2)获取数组元素
Console.WriteLine(array[0][1]);
(3)修改交错数组中得元素
array[0][1]=99;
Console.WriteLine(array[0][1]);
(4)遍历交错数组
for(int i=0;i<array.GetLength(0);i++)
{
for(int j=0;j<array[i].Length;j++)
{
Console.Write(array[i][j]+" ");
}
Console.WriteLine();
}
1.4值类型和引用类型
变量类型的复习:
无符号整型:byte b=1;
ushort us=1;
uint ui=1;
ulong ul=1;
有符号的整型:
sbyte sb=1;
short s=1;
int i=1;
long l=1;
浮点数:
float =1f;
double d=1.1;
decimal de=1.1m;
特殊类型:
boolbo=true;
char c='A';
string str="strs";
复杂数据类型
enum 枚举
数组(一维,二维,交错)
把以上类型进行分类
引用类型:string,数组,类
值类型:其他,结构体
值类型与引用类型的区别:
(1)使用上的区别
//值类型
int a=10;
//引用类型
int[]arr=new int[]{1,2,3,4};
//声明了一个b让其等于之前的a;
int b=a;
//声明一个arr2让其等于之前的arr
int[] arr2=arr;
Console. WriteLine("a={0},b={1}",a,b);
Console. WriteLine("arr[0]={0},arr2[0]={1}",arr[0],arr2[0]);
此时输出相同(a=10,b=10;arr[0]=1,arr2[0]=1)
b=20;
arr2[0]=5;
Console. WriteLine("a={0},b={1}",a,b);
Console. WriteLine("arr[0]={0},arr2[0]={1}",arr[0],arr2[0]);
此时输出(a=10,b=20;arr[0]=5,arr2[0]=5)
总结:值类型在相互赋值时,把内容拷贝给了对方,它变我不变
引用类型的相互赋值时让两者指向同一个值,它变我也变
为什么值类型和引用类型在使用不同?
值类型存储在栈空间——由系统分配,自动回收,小而快
引用类型 存储在堆空间——手动申请和释放,达大而慢

注意:如果上面不是相互赋值而是arr2=new int[]{99,1,2,3};
则arr2指向新地址,不和arr相同了
1.5特殊引用类型——string
string的它变我不变
string str1="123";
string str2=str1;
//因为string是引用类型,按理说,应该是它变我也变
str2=str1;
str2="321";
Console.WriteLine(str1);
Console.WriteLine(str2);
//这时可以发现str2改变了,这是因为c#对string做了特殊的处理,使得string可以拥有值类型的它变我不变得特征,string在赋值得时候会在堆上重新分配空间
//string虽然方便,但是有一个缺点,就是频繁得改变string,重新赋值,会产生内存垃圾,影响性能
补充:监视内存方法
打开调式->窗口->监视->在监视窗口中监视想监视得内容(如:str1,str2,&str1,&str2)
1.6函数
基本概念 :
本质是一块具有名称得代码块,可以使用函数(方法)得名称来执行该代码块,是封装代码进行重复使用得一种机制。
主要作用:
1.封装代码
2.提升代码复用率
3.抽象行为
函数声明在哪里?
可以写在class和struct语句块中
static 返回类型 函数名(参数类型 参数名1,参数类型 参数名2,参数类型 参数名1,...)
{
函数逻辑;
函数逻辑;
return 返回值;(如果有返回类型才返回)
}
1.关于static 不是必须的 在没有学习类和结构体之前,都必须写
2.返回类型可以返回任意的变量类型,14种变量类型+复杂数据类型(枚举,数组,结构体,类)
3.函数名 使用帕斯卡命名法,MyName(每个拼写首字母大写)
4.参数不是必须的,可以有0-n个参数,参数类型也可以是任意的,多个参数需要用逗号隔开
5.当返回值不是void时必须通过新的关键词return返回对应类型的内容(即使是void也可以选择性使用)
实际运用:
(1)无参无返回值函数
static void SayHellow()
{
Console.WriteLine("你好世界");
return;
}
//使用函数直接写函数名(参数)即可
(2)有参无返回值函数
static void SayYourName(string name)//参数必须传一个能得到对应类型的表达式
{
Console.WriteLine("你的名字是:{0}",name);
return;
}
(3)无参有返回值函数
static string WhatYourName()
{
return "Nilcela";
}
(4)有参有返回值函数
static int Sum(int a,int b)
{
int c=a+b;
return c;
//以上两步可以合并为return a+b;
}
(5)有参有多返回值函数
//传入两个数,然后返回两个数的和以及他们俩的平均数
//函数的返回值一定是一个类型,只能是一个内容
static int[] Calc(int a,int b)//返回值可以是任意类型
{
int sum=a+b;
int avg=sum/2;
//如果使用数组作为返回值出去,那么前提是使用者知道这个数组的规则
int[] arr={sum,avg};
return arr;
//return new int[] {sum,avg};
}
(6)关于return
return可以不执行之后的代码,直接返回到函数外部
比如:
static string Speak(string str)
{
if(str=="脏话")//可以用来屏蔽脏话,这样return了就不会被打印出来
{
return;
}
Console.WriteLine(str);
}
1.7ref和out
(1)学习ref和out的原因
它们可以解决在函数内部改变外部传入的内容 里面变量 外面也要变
比如://声明一个函数
static void ChangeValue(int value)
{
value=3;
}
//使用它
int a=1;
ChangeValue(a);
Console.WriteLine (a);
//这里输出的a的值还是1;
//原因分析:这里声明a相当于在栈空间里分配了一块空间,里面赋值为1,然后传参相当于在栈中又为Value分配了一个空间,然后该将a所在地址存放的值拷贝到Value所指的地区中,所以改变value的值对于a并未有任何作用。
如果传入的是引用类型:
static void ChangeArrayValue(int []arr)
{
arr[0]=99;
}
//调用
int[] arr2={1,2,3};
ChangeArrayValue(arr2);
Console.WriteLine(arr2[0]);
//这时你会发现arr2[0]发生了改变,这是因为数组都是引用类型,传值时指向堆中同一位置,会发生改变。
//如果
static void ChangeArray(int[] arr)
{
arr=new int[]{10,20,30};
}
//调用
ChangeArray(arr2);
Console.WriteLine(arr2[0]);
//这时会发现,arr2[0]并未改变,因为,虽然传值时arr指向了arr2的地址,但是函数里arr重新开辟了一个空间,指针指向不再是arr2的地址,所以相当于没有改变arr2。
如果我们希望能够既改变里面也改变外面就需要用到ref和out
(2)ref和out的区别
//ref和out是函数参数的修饰符,当传入的值类型在内部改变,或者引用类型参数在内部重新声明时,外部的值会发生变化
//ref的使用
static void ChangeValueRef(ref int value)
{
value=3;
}
//调用
int a=1;
ChangeValueRef(ref a);
Console.WriteLine(a);
//此时会发现a的值被改变了
**************************************************
static void ChangeArrayRef(ref int[] arr)
{
arr=new int[]{10,20,30};
}
//调用
int []arr2={1,2,3};
ChangeArrayRef(ref arr2);
Console.WriteLine(arr2[0]);
//此时会发现数据已被改变
*****************************************************
//out关键字
static voidChangeArrayOut( out int[] arr)
{
arr= new int[]{999,888,777};
}
static void ChangeValueOut(out int value)
{
value=99;
}
//调用
ChangeValueOut(out a);
Console.WriteLine(a);
ChangeArrayOut(out arr2);
Console.WriteLine(arr2[0]);
//效果是一样的
(3)ref和out的区别
1.ref传入的变量必须初始化 out 不用
2.out传入的变量必须在内部赋值 ref 不用
***************************************************
//对于第一句话举例:
int a=1;//如果用这个初始化后的,则两个都可以用
int a;//如果未初始化,则ref关键字不能用
ChangeValueRef(ref a);
CahngeValueout(out a);
**************************************************
//对于第二句话举例:
static void ChangeArrayRef(ref int[] arr)
{
arr=new int[]{10,20,30};
}
static voidChangeArrayOut( out int[] arr)
{
//arr= new int[]{999,888,777};如果这里不赋值,那么就会报错,因为结合第一点来看,传进来的数组在外面可能并没有初始化,如果函数内部再不对其进行初始化的话,那么相当于没有用,所以out内部必须赋值,与之相反,由于第一条规则,ref必须传入的值经过了初始化,所以内部可以不给它赋值。
}
总结:
1.ref和out的作用:解决值类型和引用类型在函数内部改值,或者重新声明能够影响外部传入的变量,让其也被修改。
2.使用上就是声明参数和调用时在参数前加上相应的关键字
3.ref传入的变量必须已被初始化,out传入的变量在函数内部必须被修改(防止未被初始化的情况)
****************************************************************
1.8变长参数和参数默认值
(1)变长参数关键字 params
static int Sum(params int[] arr)
{
int sum=0;
for(int i=0;i<arr.Lenght;i++)
{
sum+=arr[i];
}
return sum;
}
//调用
Sum(1,2,3,4,4,6,73,2424,54141);//只要参数类型和指定的参数类型一致,可以写任意多个;
//params只对参数起作用
注意:
1.params关键字后面必为数组
2.数组的类型可以是任意类型
3.函数参数可以有别的参数和params关键字修饰的参数
4.函数参数中最多只能出现一个params关键字,并且一定是在最后一组参数,前面可以有n个参数
****************************************************************
(2)默认参数
//有参数默认值的参数一般称为可选参数
作用是当调用函数时可以不传入参数,不传入就会使用默认值作为参数的值
//使用举例:
static void Speak(string str="我没什么话可说");
{
Console.WriteLine(str);
}
//调用
Speak();//这里会输出默认值
Speak("你好!");//这里会输出传入的值
*************************************************************************
注意:
1.支持多参数默认值,每个参数都可以有默认值
2.如果要混用,可选参数必须写在普通参数后面
对于第一点的解释:
Speak(string test,string name="我",string str="说话")
{}
对于第二点
Speak(string test,string name="我",string str="说话",string test2)
{}
//这里如果在这里默认参数后面加了普通参数,那么在使用时会出现歧义
比如调用:
Speak("1","2");//这里第二个参数到底是传给name还是传给最后一个参数test2呢?
*********************************************************************
总结:
1.变长参数关键字params
作用:可以传入n个同类型参数,n可以是0
注意:
(1).params后面必须是数组 意味着只能是同一类型的可变参数
(2).变长参数只能有一个
(3).必须在所有参数最后写变长参数
2.参数默认值(可选参数)
作用:可以不传参数,不传用默认,传了用传的
注意:
1.可选参数可以有多个
2.正常参数可写在可选参数前面,可选参数只能写在所有参数的后面
*********************************************************************
1.9函数重载
含义:在同一语句块(class或者struct)中,函数(方法)名相同,参数数量不同或者参数类型相同,但参数类型或者顺序不同
作用:命名一组功能相似的函数,减少函数名的使用,避免命名空间的污染
提升程序的可读性
举例:
注意:(1)重载和返回值类型无关,只和参数类型,个数,顺序有关
(2)调用时,程序会自己根据传入的参数类型判断使用哪一个重载
*********************************************
基本函数:
static int CalcSum(int a,int b)
{
return a+b;
}
//调用
CalcSum(1,2);
*********************************************
//参数数量不同
static int CalcSum(int a,int b,int c)
{
return a+b+c;
}
//调用
CalcSum(1,2,3);
*********************************************
//数量相同,类型不同
static float CalcSum(float a,float b)
{
return a+b;
}
CalcSum(1.2f,2.4f);
*********************************************
//数量相同,类型不同
static float CalcSum(int a,float b)
{
return a+b;
}
CalcSum(1,2.4f);
**********************************************
//数量相同,顺序不同
static float CalcSum(float b,int a)
{
return a+b;
}
CalcSum(2.4f,1);
**********************************************
//ref和out
//这里加ref和out其实也是一种参数的类型,注意同时也要满足ref和out的用法(ref前必先初始化,out内部必被赋值),但是他们不能同时修饰参数个数类型,顺序一模一样的函数
static float CalcSum(ref float b,int a)//b必须在外被初始化
{
return a+b;
}
static float CalcSum(out float b,int a)//报错,首先是不符合out在内部被赋值的特点,其次ref和out不能同时修饰相同的参数
{
return a+b;
}
********************************************************“
总结:
1.概念:同一个语句块中,函数名相同,参数数量,类型,顺序不同的函数
2.注意:与返回值不同
3.作用:一般用来处理不同参数的同一类型的逻辑处理
************************************************************
2.0递归函数
含义:函数自己掉自己
正确的递归函数的条件
1.必须有结束调用的条件
2.用于条件判断的这个条件必须改变能够达到停止的目的
************************************************************
2.1结构体
基本概念:是一种自定义变量类型,类似枚举需要自己定义
它是数据和函数的集合,在结构体中可声明各种变量和方法
作用:用来表现存在关系的数据的结合,比如用结构体表示学生 动物 人类等等
位置:一般写在namespace语句块中,关键字为:struct
结构:
struct 自定义结构体名//结构体名规范为帕斯卡命名法
{
//第一部分:变量
//第二部分:构造函数(可选)
//第三部分:函数
}
**********************************************************
实例://学生
struct Student
{
//结构体声明的变量不能直接初始化,变量类型可以写任意类型,包括结构体,但是不能是自己的结构体
public int age;
public bool sex;
public int number;
public string name;
//构造函数(可选)
//函数方法(表现数据结构的行为)注意:在结构体中的方法,目前不需要加static关键字
public void Speak()
{
Console.WriteLine("我的名字是{0},我今年{1}岁",name,age);
}
}
***********************************************
结构体的使用:
在访问标识符为公共的情况下,在主函数中调用
Student s1;
s1.age=10;
s1.sex=false;
s1.number=1;
s1.name="Nicela";
s1.Speak();
***********************************************
访问修饰符:
public 公共的 可以被外部访问
private 私有的 只能在内容使用(所谓内部使用就是在结构体中的函数中使用变量)
默认不写为private
**********************************************
结构体的构造函数
基本概念
1.没有返回值
2.函数名必须与结构体相同
3.必须有参数
4.声明了构造函数那么必须在其中对所有变量数据初始化
public Student (int age,bool sex,int number,string name)
{
this.age=age;//如果写age=age;则编译器无法区分,用this可以区分是本类的变量,不写this的age表示传进的参数
this.sex=sex;
this.number=number;
this.name=name;
}
//构造函数一般是用于在外方便对结构体进行初始化,这样通过调用构造函数,就算变量是私有的,但只要函数是公有的就可以进行变量赋值
使用:Student s2=new Student(18,true,2,"小红");
*********************************************************
浙公网安备 33010602011771号