Unity学习笔记(零基础到就业)|Chapter02:C#基础
前言
这系列的学习笔记主要是根据唐老狮的unity实战路线课程整理的,加入了自己的一些补充和理解,该课程涉及的知识内容非常多,我并未学完,而是根据就业需求挑选学习的,也对后续框架部分进行了一些修改,希望能通过整理并时常阅读这些笔记巩固开发知识,也希望能跟在学习unity的小伙伴一起分享、探讨,笔记中有疑问或出错的部分也希望大佬们能够给予指导鸭~🙏
一、复杂数据(变量)类型part01:枚举+数组
1.特点
一般是多个数据(变量)集合在一起的,并且一般可以自己取名字自定义。
枚举:整形常量 的集合
数组:任意 变量类型顺序 存储的数据集合
结构体:任意变量的数据集合,可自定义
2.枚举
(1)基本概念
概念:被命名的整形常量的集合。
作用:在游戏开发中,对象很多时候会有许多状态,综合考虑可用int来表示它的状态(例如:行走为1,待机为2,跑步为3等等),将枚举项与数字进行联系,可以方便直观分清状态的含义,增强代码的可读性。
(2)申明枚举变量
Step01:申明枚举(创建一个自定义的枚举类型)
【注1】一般不会去赋值,默认第一个整形常量值是0,往后累加
【注2】申明枚举的位置:常在namespace语句块中,也可申明在class/结构体中,但不能申明在函数语句块中
c
enum E_自定义枚举名
{
自定义枚举名1, //默认是0
自定义枚举名2=5, //如果更改了就变为从5开始累加了
自定义枚举名3, // 6
}
Step02:使用申明的自定义枚举类型创建一个枚举变量
【注】枚举变量常常和分支语句配合使用(如果没有枚举值的话,就要自定义playertype=1表示什么,等于2表示什么,代码的可读性就不强了)
(3)枚举的类型转换
枚举和int互转:括号强转
c
//枚举转int
int i=(int)playerType;
//int转枚举
playerType=0;
枚举和string互转:
c
//枚举转string
string str=playerType.ToString();
//string转枚举
playerType=(E_playerType)Enum.Prase(typeof(E_playerType),"Other");
//Parse后 第一个参数:你要转为的是哪个类型 第二个参数:用于转换的对应枚举项的字符串
//转换完毕后是一个通用类型,还需要用括号强转成我们想要的目标枚举类型
3.一维数组
【注】(一般一维数组简称为数组):有序存储相同类型数据的集合
(1)数组的声明(5种方式)
(2)数组的使用
A、获取长度:
数组变量名.length
B、获取数组中的元素(索引值从0开始,一定注意不能越界,索引不能超过数组范围,即0~length-1):
数组变量名[索引值]
C、修改数组元素(同一类型的值):
数组变量名[索引值]=数值
D、遍历数组,for循环获得下标:
c
for (int i=0;i<array.length;i++)
{
Console.WriteLine(array[i]);
}
E、增加/删除数组元素:数组初始化后不能增删元素,非要增删元素只能申明新的数组,用for循环遍历原数组,将其元素存入新数组中
c
int[] array2=new int[6];
//搬家
for(int i=0;i<array.length;i++)
{
array2[i]=array[i];
}
array=array2;
G、查找数组元素,即查找某个数组元素在数组中的什么位置。通过遍历去确定数组中是否存储了这个目标元素
c
int a=3;
for(int i=0;i<array.length;i++)
{
if(a==array[i])
{
Console.WriteLine("和a相等的元素在{0}索引位置",i);
break;
}
}
4.二维数组
二维数组:使用2个下标(索引)来确定元素的数组,2个下标可以理解为行标和列标,类似于矩阵。
作用:一般用来存储矩阵,在控制台小游戏中可以用二维数组来表示地图格子
(1)声明二维数组变量(类似一维数组)
(2)二维数组的使用
A、获取长度:GetLength方法
c
//得到多少行
Console.WriteLine(array.GetLength(0));
//得到多少列
Console.WriteLine(array.GetLength(1));
}
B、获取数组中的元素
c
Console.WriteLine(array[0,1]);
Console.WriteLine(array[1,2]);
}
C、修改数组元素(同一类型的值)
c
array[0,0]=99;
Console.WriteLine(array[0,0]);
}
D、遍历(两层循环嵌套)
c
for(int i=0;i<array.GetLength(0);i++)
{
for(int j=0;j<array.GetLength(1);j++)
{
Console.WriteLine(array[i,j]);
}
}
E、增加/删除数组元素与一位数组类似
c
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];
}
}
F、查找数组元素:类似一维数组
5.交错数组(非重点知识,了解就好)
交错数组是数组的数组,每个维度数量可以i不同,二维数组的每行的列数都是相同的,但交错数组每行列数可以不同
(1)交错数组的声明
(2)交错数组的使用
A、获取长度
c
//得到多少行
Console.WriteLine(array.GetLength(0));
//得到某一行的列数
Console.WriteLine(array[0].length);
}
B、获取和修改元素
c
//注意:不要越界
//获取
Console.WriteLine(array[0][1]);
//修改
array[0][1]=99;
Console.WriteLine(array[0][1]);
}
C、遍历交错数组
c
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();
}
6.补充:关于数组的常见考题
(1)数组和List的区别
从大小可变性和内存分配上 :List的本质是一种动态的数组。它可以通过自动扩展内部数组的容量来实现动态增长,当元素被添加到List中时,如果容量不足,它会重新分配一块更大的内存,并将原有元素复制到新的内存中,也由于List需要进行内存重新分配和元素复制,频繁地添加或删除元素可能会导致性能下降。
而Array(数组)在创建时需要指定固定容量,并且无法直接改变容量,若需要扩容,就要创建一个新的更大容量的数组,将原数组元素复制到新的数组中。
从可使用的方法上来说 :list有更多的方法如add、remove、insert等,对元素操作更方便。
从使用场景上来说:需要动态增减元素的情况,或者不确定具体需要多少存储空间的时候用list,主要用于索引访问和遍历,而不需要更改内存大小,且对性能有较高要求时使用数组。
(2)数组参数是什么
数组参数指的是将数组作为参数传递给方法或函数。
当数组作为参数传递给方法时,实际上是传递了数组的引用。这意味着在方法内部对数组所做的任何修改都会影响到原始数组,也就是说方法可以访问和修改数组的元素,并且可以获取数组的长度和维度信息,最后方法可以返回对数组元素的修改,因为传递的是引用。
二、值类型和引用类型
1.存储和使用上的区别
存储上 :值类型------存储在栈上,大小由系统分配,自动回收,小而快;
引用类型------存储在堆上,赋值时拷贝的是地址,手动申请和释放,大而慢
使用上:值类型------它变我不变;引用类型------它变我也变
假设值类型对象a赋值给了b,相当于是将内容拷贝给了b,其中a的值发生了改变,b的值不会跟着改变,而引用类型对象a赋值给了b,其实只是将引用地址赋值给了b,两者指向同一个值,若a的值发生了改变,b的值也会改变。
2.特殊的引用类型:String
String是引用类型,但在赋值使用时也是它变我不变的,原因是字符串的值存储在静态存储区,引用地址存储在栈,就算修改了字符串的值,也是在静态存储区中新分配了一块区域存储修改后的值,原先的引用地址发生了改变,指向新的值。因此假设字符串对象a赋值给了b,其中a的值发生了改变,是指a的引用地址改变了,指向了新的值,b的引用地址跟存储的值都不变 。
3.补充:常见考题
除了上述值类型与引用类型的区别、string为什么是特殊的引用类型外,还可能问到:
(1)String和StringBuilder的区别
string每次修改拼接时会重新分配内存空间,产生垃圾,而stringBuilder修改字符串时不会创建新的对象,因此需要频繁修改和拼接的字符串可以用stringBuilder,可以提升性能。但string提供了更多方法供使用,需要使用这些特殊方法来处理一些特殊逻辑时可以使用string。
(2)深拷贝和浅拷贝
浅拷贝复制对象的引用,导致原始对象和拷贝对象共享相同的数据;
深拷贝创建一个新对象并复制所有属性值,使得原始对象和拷贝对象独立开来,对其中一个对象的修改不会影响另一个对象。
三、函数(方法)
1.函数基础
(1)基本概念
函数(也称方法)本质是一块具有名称的代码块,可以使用函数的名称来执行该代码块,函数时封装代码进行重复使用的一种机制,提升代码的复用率
(2)函数写的位置
A、class语句块中
B、struct语句块中
【注】不能在函数中定义函数!
(3)基本语法
c
static 返回类型 函数名(参数类型 参数名1,参数类型 参数名2,...)
{
函数的代码逻;
return 返回值; //如果有返回值类型才返回
}
//【说明】
//1、关于static 不是必须的,在没有学习类和结构体之前都是必须写的
//2、关于返回值类型引出一个关键字:void(表示没有返回值类型)
//3、返回类型可以写任意的变量类型
//4、关于函数名,一般使用帕斯卡命名法: MyName(帕斯卡命名法) myName(驼峰命名法,一般用于变量命名)
//5、参数不是必须的,可以有0~n个参数,参数也可以是任意类型的,有多个参数时需要用逗号隔开
//6、返回值类型不是void时,必须用新的关键词 return 返回对应类型的内容(即使是void也可以选择性使用return)
(4)实际运用
A、无参无返回值
在class中定义函数:
c
static void SayHello()
{
Console.WriteLine("Hello world");
//return ;
}
在main函数中调用该函数:
c
static void Main(string[] args)
{
SayHello();
}
B、有参无返回值
定义:
c
static void SayYourName(string name)
{
Console.WriteLine("your name is:{0}",name);
//return ;
}
调用:
c
static void Main(string[] args)
{
string str="小梁";
SayYourName(str);
}
C、无参有返回值
定义:
c
static string WhatYourName()
{
return "小梁";
}
调用:一般直接拿返回值来用(这里例子用了函数嵌套),要不就是拿变量接收他的结果
c
static void Main(string[] args)
{
//直接拿返回值来用,比如这里作为参数传入其他函数中
SayYourName(WhatYourName());
//拿变量接收结果
string str2=WhatYourName()
}
D、有参有返回值
定义:
c
static int Sum(int a,int b)
{
return a+b;
//【注】return后可以是一个值,也可以是表达式,只要表达式的结果和返回值类型是一致的就行
}
调用:
c
static void Main(string[] args)
{
Console.WriteLine(Sum(2,5));
}
E、有参有多个返回值
定义:
c
static int[] Calc(int a,int b)
{
int sum=a+b;
int avg=sum/2;
return new int[] {sum,avg};
}
调用:
c
//如果是数组作为返回值,那么前提是使用者知道这个数组的规则
int[] arr=Cals(5,7);
Console.WriteLine(arr[0]+" "+arr[1]);
(5)关于return
即使函数没有返回值,也可使用return;
return可以直接不执行之后的代码,直接返回到函数外部(提前结束函数逻辑)
c
static viod Speak(string str)
{
if(str=="混蛋")
{
return;
}
Console.WriteLine(str);
}
c
static string Speak(string str)
{
if(str=="混蛋")
{
return "";
}
return str;
}
2.ref和out:函数参数的修饰符
(1)使用ref和out的原因
ref和out都是对函数参数的修饰符,起到的作用都是使参数在函数内部改值或重新声明时,能够将外部传入的这个变量也跟着被更改。
比如定义一个这样的函数:
c
static void ChangeValue(int value)
{
value=3;
}
传入a=1调用函数,a的值还是1:
c
int a=1;
ChangeValue(a);
Console.WriteLine(a);
原因就在于函数传入参数值时相当于是将a赋值给了value (value=a),结合值类型和引用类型的知识,value的值变了,a是不会变的。要做到value变a也跟着变,就要用到ref和out修饰符
(2)ref和out的使用及区别
c
static void ChangeValueRef(ref int value)
{
value=3;
}
static void ChangeValueOut(out int value)
{
value=99;
}
ref和out的区别在于:使用ref,传入的变量必须初始化,但在内部可改可不改;使用out,传入的变量不用初始化,但必须在内部赋值
3.变长参数和参数默认值
(1)变长参数关键字:params
c
static int Sum(params int[] arr)
{
int sum=0;
for(int i=0;i<arr.Length;i++)
{
sum+=arr[i];
}
return sum;
}
调用:
c
static void Main(string[] args)
{
Console.WriteLine("变长参数和参数默认值");
Sum();
Sum(1,2,3,4,5,6,7);
}
【注1】params int[ ] 意味着可以传入n个int参数,n可以为0,传入的参数会存在数组中
【注2】params后面必须接数组
【注3】数组的类型可以是任意类型
【注4】参数可以有别的参数和params修饰的参数
【注5】参数中只能最多出现一个params关键字,并且一定在最后一组参数的后面,前面可以有n个其他参数
(2)参数默认值
有默认值的参数称为可选参数,其作用是 当调用函数时可以不传入参数,不传就会使用默认值作为参数的值
c
static void Speak(string str="无话可说")
{
Console.WriteLine(str);
}
调用:
c
static void Main(string[] args)
{
Speak();
Speak("我有好多话要说");
}
【注1】支持多参数默认值,每个参数都可以设置默认值
【注2】若要混用普通参数和可选参数,那可选参数必须写在普通参数后面
4.函数重载
(1)基本概念
在同一语句块(class或struct)中,函数名相同,但参数数量不同 或者 参数数量相同但参数类型或顺序不同
(2)作用
命名一组功能相似的函数,减少函数名的数量,避免命名空间的污染,提升程序可读性
(3)实例
【注1】重载与返回值类型无关,只和参数的个数、类型、顺序有关。在调用时程序会自己根据传入参数的类型判断使用哪个函数进行重载
【注2】加ref、out、params也可以算作重载。但用了ref就不能用out修饰同一参数,反之亦然,可选参数不能算重载
5.递归函数
基本概念:让函数自己调用自己
【注】一个正确的递归函数必须要有能结束调用的条件
四、复杂数据(变量)类型------结构体
1.基本概念与作用
基本概念:结构体是一种自定义变量类型(类似枚举需要自己定义),它是数据和函数的集合,在结构体中可以申明各种变量和方法。
作用:用来表现存在关系的数据集合
2.基本语法
定义位置:一般申明写在namespace中,使用位置在函数中
作用:将变量包裹到结构体内部,将一些方法封装在结构体中
关键字:struct
访问修饰符(默认不写为private):
private:私有的,只能在内部使用
public:公有的,可以被外部访问
语法:
c
struct 自定义结构体名
{
//第一部分
//变量
//第二部分
//构造函数(可选)
//第三部分
//函数
}
【注1】结构体的名字规范是帕斯卡命名法
【注2】结构体中变量不能初始化,变量类型可以写任意类型,但不能是自己的结构体
3.实例
声明:
使用:
4.结构体的构造函数
(1)基本概念与作用
结构体的构造函数:
a.是一个函数,但没有返回值
b.并且函数名必须和结构体名相同
c.必须有参数
d.如果申明了构造函数,那么必须在其中对所有变量数据初始化
作用:帮助使用者快速初始化结构体对象
(2)实例
声明:
调用: