一、复杂数据类型
1. 枚举
1.1 基本概念
1.1.1 枚举是什么
枚举是一个被命名的整型常量的集合,一般用它来表示状态、类型等等
1.1.2 申明枚举和申明枚举变量
申明枚举和申明枚举变量是两个概念
申明枚举:相当于是创建一个自定义的枚举类型
申明枚举变量:使用申明的自定义枚举类型,创建一个枚举变量
1.1.3 申明枚举语法
枚举名,以E或E_开头作为命名规范
enum E_自定义枚举名
{
自定义枚举项1, //枚举中包裹的整型常量,第一个默认值是0,也可以赋整数值,后面的依次加1
自定义枚举项2,
自定义枚举项3
}
1.2 在哪里申明枚举
在namespace语句块中声明枚举(常用),也可以在class和struct语句块中声明(不常用)。注意枚举不能在方法语句块中声明
1.3 枚举的使用
E_枚举名 变量名 = E_枚举名.枚举项;
经常配合switch语句使用。
1.4 枚举的类型转换
1.4.1 枚举和int转换
int i=(int)E_枚举名.枚举项;
E_枚举名 变量名=i1;
1.4.2 枚举和string转换
string str=E_枚举名.枚举项.ToString();
playerType=(E_PlayerType)Enum.Parse(typeof(E_PlayerType),"Other"); //Parse后第一个参数:你要转为的是哪个枚举类型;第二个参数:用于转换的对应枚举项的字符串。
1.5 枚举的作用
一个项目中,一个对象可能会有许多状态,枚举可以帮助我们分清楚状态的含义。
2. 数组
数组是存储一组相同类型数据的集合。数组分为一维、多维、交错数组,一般情况下一维数组就简称为数组
2.1 一维数组
2.1.1 数组的申明
变量类型[] 数组名; //只是声明了一个数组,但是没有开辟空间
int[] arr1;
变量类型[] 数组名=new 变量类型[数组的长度];
int[] arr2=new int[5];
变量类型[] 数组名=new 变量类型[数组的长度]{内容1,内容2,内容3,....};
int[] arr3=new int[5]{1,2,3,4,5}; //大括号内内容数量必须和中括号内数字一样
变量类型[] 数组名=new 变量类型[]{内容1,内容2,内容3,....};
int[] arr4=new int[]{1,2,3,4,5};
变量类型[] 数组名={内容1,内容2,内容3,....};
int[] arr5={1,2,3,4,5};
2.1.2 数组的使用
int[] array={1,2,3,4,5};
2.1.2.1 数组的长度
变量名.Length array.Length
2.1.2.2 获取数组中的元素
数组中的下标和索引是从零开始的,注意不要越界。 array[i]
2.1.2.3 修改数组中的元素
array[5]=6;
2.1.2.4 遍历数组
for(int i=0;i<array.Length;i++){}
2.1.2.5 增加数组中的元素
int[] array1=new int[6];
for(int i=0;i<array.Length;i++)
{
array1[i]=array[i];
}
array1[5]=6;
array=array1;
2.1.2.6 删除数组中的元素
int[] array1=new int[5];
for(int i=0;i<array1.Length;i++)
{
array1[i]=array[i];
}
array=array1;
2.1.2.7 查找数组中的元素
for(int i=0;i<array.Length;i++)
{
if(a==array[i])
{Console.WriteLine(i); break;}
}
2.2 二维数组
2.2.1 基本概念
二维数组是使用两个下标(索引)来确定元素的数组,两个下标可以理解为行标和列标。
比如矩阵
1 2 3
4 5 6
可以用二维数组int[2,3]表示
2.2.2 二维数组的申明
变量类型[,] 二维数组变量名;
int[,] arr1;
变量类型[,] 二维数组变量名 = 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},{4,5,6},{7,8,9}};
变量类型[,] 二维数组变量名 = 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[,] arr5={{1,2,3},{4,5,6},{7,8,9}};
2.2.3 二维数组的使用
int[,] array={{1,2,3},{4,5,6}};
2.2.3.1 二维数组的长度
得到行:array.GetLength(0)
得到列:array.GetLength(1)
2.2.3.2 获取二维数组中的元素
数组中行列的下标和索引都是从零开始的,注意不要越界。 array[i,j]
2.2.3.3 修改二维数组中的元素
array[0,0]=99;
2.2.3.4 遍历二维数组
for(int i=0;i<array.GetLength(0);i++)
{
for(int j=0;j<array.GetLength(1);j++){}
}
2.2.3.5 增加二维数组中的元素
int[,] array1=new int[3,3];
for(int i=0;i<array.GetLength(0);i++)
{
for(int j=0;j<array.GetLength(1);j++)
{
array1[i,j]=array[i,j];
}
}
array1[2,0]=7; array1[2,1]=8; array1[2,2]=9;
array=array1;
2.2.3.6 删除二维数组中的元素
int[,] array1=new int[2,3];
for(int i=0;i<array1.GetLength(0);i++)
{
for(int j=0;j<array1.GetLength(1);j++)
{
array1[i,j]=array[i,j];
}
}
array=array1;
2.2.3.7 查找二维数组中的元素
for(int i=0;i<array.GetLength(0);i++)
{
for(int j=0;j<array.GetLength(1);j++)
{
if(a==array[i,j])
{Console.WriteLine($"{i}、{j}"); break;}
}
}
2.3 交错数组
2.3.1 基本概念
交错数组是数组的数组,每个维度的数组可以不同。注意,二维数组每行的列数必须相同
2.3.2 交错数组的申明
变量类型[][] 交错数组名;
int[][] arr1;
变量类型[][] 交错数组名=new 变量类型[行数][];
int[][] arr2=new int[3][];
变量类型[][] 交错数组名=new 变量类型[行数][]{一维数组1,一维数组2....};
int[][] arr3=new int[3][]{new int[]{1,2,3},new int[]{4,5},new int[]{6}};
变量类型[][] 交错数组名=new 变量类型[][]{一维数组1,一维数组2....};
int[][] arr4=new int[][]{new int[]{1,2,3},new int[]{4,5},new int[]{6}};
变量类型[][] 交错数组名={一维数组1,一维数组2....};
int[][] arr5={new int[]{1,2,3},new int[]{4,5},new int[]{6}};
2.3.3 交错数组的使用
int[][] array={new int[]{1,2,3},new int[]{4,5}};
2.2.3.1 交错数组的长度
得到行:array.GetLength(0)
得到列要具体到哪一行的列:array[0].Length
2.2.3.2 获取交错数组中的元素
数组中行列的下标和索引都是从零开始的,注意不要越界。 array[i][j]
2.2.3.3 修改交错数组中的元素
array[0][0]=99;
2.2.3.4 遍历交错数组
for(int i=0;i<array.GetLength(0);i++)
{
for(int j=0;j<array[i].Length;j++){}
}
二、值类型和引用类型
1. 使用和存储上的区别
1.1 类型分类
1.2 值类型和引用类型的区别
值类型存储在栈空间——系统分配,自动回收,小而快
引用类型存储在堆空间,栈空间中记录堆的地址——手动申请和释放,大而慢
值类型在相互赋值时,把栈上的内容拷贝给了对方,当再次修改时,互不干扰。
引用类型在相互赋值时,把栈上指向堆的地址拷贝给对方,当再次修改堆的内容时,两个都会改变
2. 特殊的引用类型string
string类型在堆中的内容不可被修改,每次赋值都是堆上重新开辟空间再在栈上存新的地址。所以当string类型在相互赋值时,一个改变另一个不会跟着改变。
三、方法
1. 方法基础
1.1 基本概念
函数也称方法,本质是一块具有名称的代码块。方法是封装代码进行重复使用的一种机制。
方法的主要作用:
- 封装代码
- 提升代码复用率
- 抽象行为
1.2 方法写在哪里
class语句块中
struct语句块中
1.3 基本语法
static 返回类型 方法名(参数类型 参数名1,参数类型 参数名2...)
{
代码逻辑;
return 返回值;
}
- 如果返回类型是void表示没有返回值,return也可以不写
- 关于方法名使用帕斯卡命名法命名
- 参数不是必须的,可以有0~n 个参数,参数的类型也可以是任意类型
- 关于参数名使用驼峰命名法命名
- 当返回值类型不为 void 时,必须通过新的关键词 return 返回对应类型的内容
- 函数也可以嵌套使用
1.4 关于return
即使方法没有返回值,我们也可以使用 return。return 可以直接不执行之后的代码,直接返回到函数外部
2. ref和out
当方法传入参数时,实际上是传入了参数的临时拷贝。导致在传入值类型参数时,在方法内部改变其数值后无法影响方法外部本身的数值;而传入引用类型参数时,参数拷贝的是堆的地址,当在方法内部改变其数值后,同样会影响方法外部本身的值(但是当方法内的引用类型重新声明new一个对象后,外部也将无法改变)。
他们可以解决在方法内部改变外部传入的内容。里面变了,外面也会变
2.1 ref和out的使用
函数参数的修饰符,当传入的值类型参数在内部修改时或引用类型参数在内部重新声明时,外部的值会发生变化
static void ChangeValueRef(ref int value)
{
value=100;
}
static void ChangeArrayRef(ref int[] array)
{
array=new int[]{100,200,300};
}
static void ChangeValueOut(out int value)
{
value=999;
}
static void ChangeArrayOut(out int[] array)
{
array=new int[]{999,888,777};
}
static void Main()
{
int a=1;
ChangeValueRef(ref a);
ChangeValueOut(out a);
int[] b=new int[]{1,2,3};
ChangeArrayRet(ref b);
ChangeArrayOut(out b);
}
2.2 ref和out的区别
- ref 传入的变量必须提前初始化,out不用
- out 传入的变量必须在内部赋值,ref不用
3. 变长参数和参数默认值
3.1 变长参数关键字
static int Sum(params int[] arr)
{
int sum=0;
for(int i=0;i<arr.Length;i++)
{
sum+=arr[i];
}
return sum;
}
static void Main()
{
Sum(1,2,3);
}
params int[]意味着可以直接传入 n 个 int 参数而不需要先声明数组。
注意:
- params关键字后面必须为数组
- 数组的类型可以是任意类型
- 方法参数可以有别的参数和 params 关键字修饰的参数
- 方法参数中最多只能出现一个 params 关键字,并且一定是在最后一组参数,前面可以有 n 个其他参数
3.2 参数默认值
有参数默认值的参数一般称为可选参数。作用时当调用方法时可以不传入参数,不传就会使用默认值作为参数的值。
注意:
- 支持多参数默认值,每个参数都可以有默认值
- 如果要混用,可选参数必须写在普通参数后面
4. 方法重载
重载概念:在同一语句块中,方法名相同,参数的数量不同,或者参数的数量相同但参数的类型、顺序不同。
作用:
- 命名一组功能相似的方法,减少方法名的数量,避免命名空间的污染
- 提升程序可读性
注意:
- 重载和返回值类型无关,只和参数类型、个数、顺序有关
- 调用时,程序会自己根据传入的参数类型判断使用哪一种重载
- 加ref或out也会视为是另一种参数类型,但声明了含ref参数的方法后再声明相同的含out参数的方法时会视为出错
5. 递归方法
就是让函数自己调用自己
注意:
- 一个正确的递归函数必须有结束调用的条件
- 用于判断结束的条件必须能够达到停止的目的
四、复杂数据类型——结构体
1. 基本概念
结构体是一种自定义变量类型,它是数据和方法的集合。在结构体中可以声明各种变量和方法
作用:用来表现存在关系的数据集合,比如用结构体表现学生、动物、人类等。
2. 基本语法
- 结构体一般写在命名空间语句块中
- 结构体关键字 struct
struct 自定义结构体名 //帕斯卡命名法
{
//变量
//构造函数(可选)
//方法
}
3. 访问修饰符
修饰结构体中变量和方法是否能被外部使用
- public:共有的 所有的类都可以访问
- private:私有的 当前类内部可访问
- protected:受保护的 当前类以及继承它的子类可访问
- internal:内部的 只限于本项目内访问,其他的不能访问
- protected internal:内部保护访问 只能是本项目内部或子类访问 其他类不能访问
4. 结构体的使用
struct Student
{
public int age;
public string name;
public void Speak()
{
Console.WriteLine($"我是{this.name},我{this.age}岁了");
}
}
internal class Program
{
static void Main(string[] args)
{
Student s;
s.age=10;
s.name="zhangsan";
s.Speak();
}
}
5. 结构体的构造函数
一般是用于方便外部初始化的。
基本概念:
- 没有返回值
- 名称必须和结构体名相同
- 必须有参数
- 如果声明了构造函数,那么必须在其中对所有变量数据初始化
struct Student
{
public int age;
public string name;
public Student(int age, string name)
{
this.name =name;
this.age = age;
}
public void Speak()
{
Console.WriteLine($"我是{this.name},我{this.age}岁了");
}
}
第二个构造函数中出现this,是为了区分自己本身的字段和传入的参数,使代码更清晰。
注意:
struct 不能自己定义无参构造函数(但是在没有定义构造函数的情况下本身含有一个无参构造函数),所以也不能在声明结构体内直接给字段赋初值。
五、排序初探
排序是计算机内经常进行的一种操作,其目的是将一组无序的记录序列调整为有序的记录序列
1. 冒泡排序
两两相邻,不停比较不停交换,比较Length-1轮
static void ArraySort(int[] array)
{
int temp = 0;
bool isSort;
for (int i = 0; i < array.Length-1; i++)
{
isSort = false;
for (int j = 0; j < array.Length - 1 - i; j++)
{
if (array[j] > array[j + 1])
{
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
isSort = true;
}
}
if (!isSort) { return; }
}
}
两层循环,外层轮数,内层每轮次数
2. 选择排序
新建中间商,依次比较找出极值,放入目标位置,比较Length-1轮
static void ArraySort(int[] array)
{
int temp = 0;
int index = 0; //假设一个最大值位置
//比较i轮
for (int i = 0; i < array.Length-1; i++)
{
//每轮比较Length - i次
for (int j = 0; j < array.Length - i; j++)
{
//依次比较找出最大值
if (array[j] > array[index])
{
index = j;
}
}
//如果最大值就是当前位置,就不用交换
if (index != array.Length - 1 - i)
{
temp = array[index];
array[index] = array[array.Length - 1 - i];
array[array.Length - 1 - i] = temp;
}
}
}
两层循环,外层轮数,内层寻找次数,初始索引,记录极值