一、结构体类型
结构体类型是一种自定义类型,用于创建我们游戏或者实际业务中的自定义类型.
代码中变量有通用的,可以使用结构体,包裹起来。
1、成员变量
/// <summary>
/// 英雄结构体
/// </summary>
struct Hero
{
//成员
public string Name;
}
使用结构体:
必须new一个出来,也就是创建一个结构体。
然后这个变量调用结构体中的字段。
private void Start()
{
Hero tanke=new Hero();//创建一个结构体
tanke.Name = "超级坦克";
Debug.Log(tanke.Name);
Hero tanke2 = new Hero();
tanke2.Name = "终极坦克";
Debug.Log(tanke2.Name);
}
2、成员函数
结构体不止能包裹成员字段还能包裹成员函数
struct Hero
{
//成员函数
public void Show()
{
Debug.Log($"我的名字是:{Name}");
}
}
也是通过打点的方法调用
private void Start()
{
Hero tanke=new Hero();//创建一个结构体
tanke.Name = "超级坦克";
tanke.Show();
Hero tanke2 = new Hero();
tanke2.Name = "终极坦克";
tanke.Show();
}
这里就相当于:
猫和狗都能吃,但两种动物的吃不相同。
3、构造函数
构造函数没有类型。
必须放入结构体中的成员字段,就和传参一样,把字段当成形参。
struct Hero
{
//成员
public string Name;
public int Attack;
//构造函数
public Hero(string name,int attack)
{
Name = name;
Attack = attack;
}
}
使用方法:
构造函数默认有一个无参构造函数;也可以在new一个构造函数的同时,直接传入参数。
private void Start()
{
Hero tanke=new Hero();//创建一个结构体
tanke.Name = "超级坦克";
tanke.Attack = 20;
tanke.Show();
Hero tanke2 = new Hero("终极坦克",200);
tanke.Show();
}
二、结构体使用细节(特性)
1、构造函数也可以重载
private void Start()
{
Hero tanke=new Hero();//创建一个结构体
Hero tanke2 = new Hero("终极坦克",200,300);
}
注:此处的单独形参,可以直接赋值
//构造函数
public Hero(string name,int attack)
{
Name = name;
Attack = attack;
}
public Hero(string name,int attack,int attackBuff)
{
Name = name;
Attack = attack+attackBuff;
}
2、this关键字
当形参与实参的变量名一致,此时需要用this。
此时,谁调用了这个方法,谁就是this。
public Hero(string Name, int Attack)
{
//同名变量的就近原则,就近:定义的位置
this.Name = Name;
this.Attack = Attack;
}
3、访问修饰符
如果结构体中的成员变量是private,外界是访问不到的,只能在结构体里面使用。
public修饰的变量,外界和内部都可以随意使用。
三、面向对象概念
本质上是一种编程思想。
核心就是:把相关的数据和行为组织成一个整体来看待。
举例子:“狗”是一个类型,但具体的“大黄”才是实体,也就是对象,所以生成对象也叫“实例化”。
四、类与对象基本语法
和结构体类似。都通过new一个方法实现
区别:结构体可以无参new,类不行
private void Start()
{
HeroClass hero = new HeroClass();
HeroStruct heroStruct = new HeroStruct();
}
class HeroClass
{
public string Name;
public int Attack;
//如果不定义构造函数,那么有一个无参构造函数
//如果定义了任意个参数的有参构造函数,那么默认的无参构造函数会失效
public HeroClass(string name,int attack)
{
Name = name;
Attack = attack;
}
public HeroClass()
{
}
}
五、继承
1、封装
将事物的数据、行为抽象成类,这个过程就是封装;类的产生过程就是一种封装;
2、继承
继承的核心意义是:获得父类中的所有成员。
父类是基类;子类是派生类。
支持多层继承,一个类只能有一个父类。
private void Start()
{
Animal animal= new Animal();
animal.Name = "动物";
animal.Eat();
Dog daHuang= new Dog();
daHuang.Name = "大黄";
Debug.Log(daHuang.Name);
daHuang.Eat();
Cat xiaobai= new Cat();
xiaobai.Name = "小白";
Debug.Log(xiaobai.Name);
xiaobai.Eat();
}
子类可以在声明一个方法后,调用父类中的对象。
//父类
public class Animal
{
public string Name;
public void Eat()
{
Debug.Log($"{Name}开始吃东西了");
}
}
//子类
public class Dog:Animal
{
}
public class Cat:Animal
{
}
六、继承后的构造函数
基类中存在的构造函数,子类必须也做对应的实现。
1、关键字
this:表示当前类型的对象
base:在程序上他的类型是基类,所以一般用来专门访问基类成员的
一般情况下,不需要用base,因为默认就可以访问基类成员。
*但是实际上在执行上this和base是同一个对象。、
七、多态
同一操作的不同行为;封装、继承、多态
1、虚函数
当基类有一个方法,而子类也有一个同名方法时,此时就需要使用关键字virtual 将基类方法设置为虚方法。
public class Animal
{
public void Cry()
{
Debug.Log($"{Name}张嘴了");
Debug.Log($"{Name}:{CryString}");
}
}
public class Dog : Animal
{
public void Cry()
{
Debug.Log($"小狗{Name}张嘴了");
Debug.Log($"{Name}:汪");
}
}
public class Cat : Animal
{
public void Cry()
{
Debug.Log($"小猫{Name}张嘴了");
Debug.Log($"{Name}:喵");
}
}
基类使用 virtual ;子类使用 override;
虚函数:
1、提供默认逻辑;
2、提供可选的逻辑;
在子类中使用base,继承了基类中的函数逻辑。
public override void Cry()
{
base.Cry();
Debug.Log($"小狗{Name}张嘴了");
Debug.Log($"{Name}:汪");
}
注:重载 重写 区别
重载:同名函数 不同参数
重写:覆盖基类中的方法
2、抽象类与抽象函数
抽象类:高度抽象,不需要实例化的类型,但是子类可以实例化
public abstract class Animal
{
}
抽象函数 :
在基类中声明抽象方法
对应的子类,必须override这个基类的方法 ;也就是在子类中实现这个抽象类
八、里氏转换
就是父类和子类型之间的一个转换
1、子类转父类
private void Start()
{
//Animal animal = new Animal(); 不允许实例化一个抽象类
Dog daHuang= new Dog("大黄","汪");
Cat xiaoBai = new Cat("小白", "喵");
DoCry(daHuang);
DoCry(xiaoBai);
}
public void DoCry(Animal animal)
{
animal.Cry();
}
这里的DoCry方法,传入了一个Animal基类变量,而不是单独的大黄小白。
这就是里氏替换中的隐式转换;相当于子类转父类
2、父类转子类
public void KejiCry(Animal animal)
{
Dog keji = (Dog)animal;
keji.Move();
}
注:父类转子类 ,强转的变量类型 应该与 传入的变量类型一致。
否则会出现类型不兼容的unity报错
九、访问修饰符和类中类
1、访问修饰符
public:公开的
private:私有的
protected:受保护的 自己以及子类才能访问
不写、默认是私有的
public abstract class Animal
{
public string Name;
private string Age;
protected string Type;
}
2、类中类
private void Start()
{
Animal animal = new Animal();
animal.Test();
}
}
//父类
public class Animal
{
public void Test()
{
//内部使用和平时没啥区别
AnimalTest animalTest=new AnimalTest();
animalTest.Test();
}
public class AnimalTest
{
public void Test()
{
Debug.Log("AnimalTest");
}
}
}
十、万物基类object
如果class 不设置基类,那么它的基类就是object
object obj=new Animal();
obj.GetType();//获取对象的实际类型
Debug.Log(obj);
这里的GetType函数,是获取变量类型的方法
源自Object
public class Animal
{
public string name;
public override string ToString()
{
return $"我是动物:{name}";
}
public Animal()
{
name = "我问";
}
}
ToString
十一 、命名空间
使用命名空间namespace
在其它的代码中调用 需要引用命名空间 using namespace
在同一个命名空间下 可以进行调用
Hello是主脚本;Animal是基类脚本;Dog是子类脚本
using UnityEngine;
using Animal;
public class Hello : MonoBehaviour
{
private void Start()
{
AnimalObject animal=new Dog();
}
}
namespace Animal
{
//父类
public abstract class AnimalObject
{
public string name;
public override string ToString()
{
return $"我是动物:{name}";
}
}
}
namespace Animal
{
public class Dog:AnimalObject
{
}
}
在调用的文件中 using命名空间即可
十二、接口 Interface
示例:机器人、鹦鹉、人、这三者都能说话 ; 分别让某个对象说话
首先分别创建这三个类的脚本 都有Talk这个方法 鹦鹉继承Animal基类
public class Robot
{
public void Talk()
{
Debug.Log("机器人说话了");
}
}
using UnityEngine;
public class Person
{
public void Talk()
{
Debug.Log("人说话了");
}
}
using UnityEngine;
public class Parrot:Animal
{
public void Talk()
{
Debug.Log("鹦鹉说话了");
}
}
public class Hello : MonoBehaviour
{
private void Start()
{
Robot robot = new Robot();
robot.Talk();
Person person = new Person();
person.Talk();
Parrot parrot = new Parrot();
parrot.Talk();
}
}
1、创建接口
共有两个接口:移动、说话
接口的代码命名使用 I 开头
在调用接口的函数中传入参数
//让某个对象进行说话
public void Talk(ITalk talker)
{
}
2、接口实现方法
这里的方法 默认公开 否则没意义
public interface ITalk
{
//实际上是抽象函数,不需要定义访问修饰符,因为没意义;不公开没法访问
void Talk()
{
}
}
3、实现功能
public class Hello : MonoBehaviour
{
private void Start()
{
Robot robot = new Robot();
Person person = new Person();
Parrot parrot = new Parrot();
//里氏转换(隐式转换)
Talk(robot);
Talk(person);
Talk(parrot);
ITalk talker = robot;//相当于这个
Robot robot1 = (Robot)talker;//显示转换(强转)
}
//让某个对象进行说话
public void Talk(ITalk talker)
{
talker.Talk();
}
}
4、总结
接口:抽象0个或多个函数,接口一定是派生类实现
十三、属性
属性就是将成员变量和成员函数相结合;
属性在外部和变量是类似的;
属性相当于提供了两个函数,分别是get、set
属性的类型决定了get的返回值类型以及set的形参类型;
属性中的两个函数get set 可以只保留一个
public class Hero
{
public int Hp;
private int mp;
public int MP
{
get
{
Debug.Log("有人在获取MP,检查是否能释放技能");
return mp;
}
set
{
if (value < mp)
{
Debug.Log("MP减少了!MP:" + value);
}
else if(value>mp)
{
Debug.Log("MP增加!MP:" + value);
}
mp = value;
}
}
}
十四、引用与Null
1、引用
引用可以理解为一个别称。
代码如下:
Start中的hero其实就是TestFunction中的unit
public class Hello : MonoBehaviour
{
private void Start()
{
Hero hero = new Hero("张三");
TestFunction(hero);
Debug.Log(hero.name);
}
void TestFunction(Hero unit)
{
unit.name += "_TestFunction";
}
}
public class Hero
{
public string name;
public Hero(string name)
{
this.name = name;
}
}
Hero hero2 = new Hero("李四");
TestFunction(hero2);
Debug.Log(hero2.name);
2、Null引用
Null 意味着空对象,也就是没有数据的对象
只定义了对象,但是没有new或者其他引用方式,默认也是null。
hero = null;//让hero无效
Debug.Log (hero.name);//null,不能调用任何成员。
Debug.Log (hero3.name);
//只有变量,没有实际引用的默认就是null
Hero hero4;
十五、引用的比较
这里new一个变量。就相当于新开辟了一个内存空间
所以这里是不相同的
int num1 = 3;
int num2 = 3;
Debug.Log(num1==num2);//True
Hero hero1 = new Hero("张三");
Hero hero2 = new Hero("张三");
Debug.Log(hero1 == hero2);
上面是值类型的比较。下面是引用类型的比较
1、对象的引用比较函数
可以使用.Equals函数来比较两个变量是否相同
Debug.Log(object.ReferenceEquals(hero1, hero2));//false
Debug.Log(object.ReferenceEquals(null, null));//True
Debug.Log(object.ReferenceEquals(hero1, null));//false
2、重写Equals函数
只是逻辑相等;和引用无关
public class Hero
{
public string name;
public Hero(string name)
{
this.name = name;
}
public override bool Equals(object obj)
{
Hero temp = (Hero)obj;
//如果obj不是Hero类型,会转换失败,temp为Null
//既然类型都不同一,那么肯定不相同
if(temp==null)return false;
else//转换成功,obj是Hero
{
return this.name==temp.name;
}
return name.Equals(temp.name);
}
}
以上是重写Equals函数的代码;
十六、值类型和引用类型
1、值类型
主要的值类型:枚举、结构体、int、float、bool等
判断相等时,一般是判断两者的值是否相等。
结论:值类型在赋值时都是拷贝值,和原先的变量无关
2、引用类型
主要的引用类型:class、interface
//在这里hero1和hero2其实是一个对象
Hero hero1=new Hero("张三",4);
Hero hero2 = hero1;
比较(相等/不等)一般是判断两者是否引用同一个对象
结论:引用类型在赋值时都是拷贝引用,和原先的变量是同一个对象
3、区分原因
值类型是轻量型数据,用完就放弃销毁。
引用类型一般是重量级数据,复制成本高。
但这不意味着引用类型不销毁,当一个对象再也没有引用时,会标记为“无用”,等待回收,但这个回收是有成本的。
class中的值类型如果进行修改,要对象名 点 成员名称
如果是引用类型,只要拿到引用,随便修改都能生效。
void SetLv(Hero hero)
{
hero.lv = 1000;
}
public class Hero
{
public string name;
public int lv;
public Hero(string name,int lv)
{
this.name = name;
this.lv = lv;
}
}
注:string不是值类型,它是特殊的引用类型
十七、特殊的引用类型String
string name = "张三Abc";
Debug.Log(name.Length);
Debug.Log(name.IndexOf("A"));
Debug.Log(name.LastIndexOf("A"));
Debug.Log(name.ToUpper());
Debug.Log(name.ToLower());
string name1 = "张三Abc";
string name2 = "张三Abc";
string name3 = new string("张三Abc");
//c#中允许重载运算符
Debug.Log(name1 == name3);
Debug.Log(object.ReferenceEquals(name1,name3));
十八、数组
数组是一种内置类型,用来管理多个数据。
数组本身是引用类型;数组中可以存放任意类型;
数组中的数据如果没有设置值,会是该类型的默认值,引用就是null;
1、定义数组
Hero hero = new Hero();
//定义数组
int[] nums1=new int[10];
int[] nums2=new int[] { 1,2,3 };
Hero[] heroes = new Hero[] {new Hero(),new Hero(),new Hero() };
//修改数据
nums1[0] = 33;
//获取数据
Debug.Log(nums1[0]);//33
//理论上不存在删除数据的情况,但是可以让数据无效
nums1[0] = 0;
heroes[0] = null;
2、打印数组数量或成员
当一个方法中传入的数组,不知道具体的数组数量时,可以使用Length方法
遍历数组的长度即可。
//打印输入数组的所有成员
private void Test(int[] nums)
{
//会输出数组的长度,但不是有效的长度,而是定义的长度
for(int i = 0; i < nums.Length; i++)
{
Debug.Log(nums[i]);
}
}
十九、交错数组
也就是数组的数组。
数组中可以存放一切,那么把数组存放在数组中,就是交错数组
int[] nums=new int[10];
int[][] nums2 = new int[10][];//10个int[]
nums2[0]=new int[10] {1,2,3,4,5,6,7,8,9,10 };
int num = nums2[0][2];//3
Debug.Log(num);
int[] nums=new int[10];
//交错数组只是约束成员都是某个类型,但是不约束里面的内容
int[][] nums2=new int[][]
{
new int[]{0,1,2,3,4,5,6,7,8,9 },
new int[]{0,1,2,3,4,5,6,7,8,9 },
new int[]{0,1,2,3,4,5,6,7,8,9 },
new int[]{0,1,2,},
};
Debug.Log(nums2[0][2]);//2
输出第零个数组的第二个参数。
int[] nums=new int[10];
//交错数组只是约束成员都是某个类型,但是不约束里面的内容
int[][] nums2=new int[][]
{
new int[]{0,1,2,3,4,5,6,7,8,9 },
new int[]{0,1,2,3,4,5,6,7,8,9 },
new int[]{0,1,2,3,4,5,6,7,8,9 },
new int[]{0,1,2,},
};
//遍历行列式
for (int i = 0; i < nums2.Length; i++)
{
for(int j = 0; j < nums2[i].Length; j++)
{
Debug.Log(nums2[i][j]);
}
}
}
二十、多维数组
确定行列的数组
//多维数组
int[,] nums3 = new int[2, 3]
{
{1,2,3 },
{4,5,6 },
};
Debug.Log(nums3[0, 0]);
Debug.Log(nums3[1, 0]);