前言
这系列的学习笔记主要是根据唐老狮的unity实战路线课程整理的,加入了自己的一些补充和理解,该课程涉及的知识内容非常多,我并未学完,而是根据就业需求挑选学习的,也对后续框架部分进行了一些修改,希望能通过整理并时常阅读这些笔记巩固开发知识,也希望能跟在学习unity的小伙伴一起分享、探讨,笔记中有疑问或出错的部分也希望大佬们能够给予指导鸭~🙏
一、面向对象编程
面向对象编程(OOP):是一种编程范式,这个程序以对象作为思考单元,以现实世界的实体和行为为模型,使得程序结构更加接近现实世界的运作方式。
面向对象编程的优势有哪些:
(1)易维护性:oop的三大特性,提高了代码的可读性和模块化程度,在维护时往往只集中在局部模块,降低了维护成本
(2)高复用性:功能被封装在一个个类中,这些类可以作为独立的实体存在,便于在不同的项目中复用,开发者可以利用现有的、已经经过测试的类来构建新的系统
(3)可扩展性:由于继承和多态的特性,开发者可以在不改变原有代码结构的基础上增加新的功能
(4)更接近日常生活和自然的思考方式,开发者可以通过对现实世界事物的理解来创建类,这样可以更快捷地编写代码,提高效率
二、面向对象编程三大特性
(一)封装
封装是指将对象的实现细节隐藏起来,只向外界暴露必要的操作接口,这样可以降低脚本间的耦合性,提高代码的安全性。具体代码中的实现包括:使用访问修饰符、定义接口或抽象类,使用委托和事件等等
1.类和对象
(1)什么是类
概念:具有相同特征、具有相同行为、一类事物的抽象。类是对象的模板,可以通过类创造出对象。
关键词:class
(2)类的声明
声明位置:namespace
类声明的语法:
class 类名
{
//特征--成员变量
//行为--成员方法
//保护特征--成员属性
//构造函数和析构函数
//索引器
//运算符重载
//静态成员
}
【注】同一个语句块中类不能重名
(3)什么是(类)对象
A、类的申明 与 类对象的申明 是两个概念,类的申明相当于申明了一个自定义变量类型(类似枚举和结构体的申明)是申明对象的模板,用来抽象显示事物的,而 对象 是类创建出来的,相当于申明一个指定类的变量,是用来表示现实中的对象个体的。
B、类创建对象的过程 一般称为实例化对象,实例化一个类对象就是在申明变量。
C、类对象都是引用类型的
(4)实例化(类)对象的语法
语法:
//类名 变量名;
//类名 变量名=null; (null代表空)
//类型 变量名=new 类名();
示例:
(5)类和结构体的区别
区别概述
存储上:
结构体和类最大的区别是在存储空间上的,结构体是值类型,类是引用类型,一个存储在栈上一个在堆上,值和引用对象在赋值上就有区别。
使用上:
结构体和类在使用上很类似,结构体具备面向对象思想中封装的特性,但是它不具备继承和多态的特性,也由于结构体不具备继承的特性,它不能使用protected访问修饰符细节区别
1、结构体是值类型,类是引用类型
2、结构体存储在栈中,类存储在堆中
3、结构体不能被继承,类可以
4、结构体成员不能使用protected访问修饰符,而类可以
5、结构体成员变量声明不能指定初始值,类可以
6、结构体不能声明无参的构造函数,类可以
7、结构体声明有参构造函数后,无参构造函数不会被顶掉
8、结构体不能声明析构函数,类可以
9、结构体需要在构造函数中初始化所有成员变量,类随意
10、结构体不能被静态static修饰(不存在静态结构体),类可以
11、结构体不能在自己内部声明和自己一样的结构体变量,类可以
如何选择结构体和类
1、想要用继承和多态时,直接淘汰掉结构体,比如玩家、怪物等
2、对象是数据集合时,优先考虑结构体,比如位置、坐标等
3、从值类型和引用类型赋值时的区别上考虑,比如经常被赋值传递的对象,并且改变赋值对象,原对象不想跟着变化时,就用结构体,比如坐标、向量、旋转等等
2.成员变量和访问修饰符
(1)成员变量基本规则
A、申明在类语句块中
B、用来描述对象特征
C、可以是任意类型的变量,数量不限
D、是否赋值根据需求来定
(2)实例
声明:
使用:(知识点:类对象实例化+成员变量初始值+成员变量使用)
3.成员方法
(1)成员方法(函数)的声明
A、申明在类语句块中
B、用来描述对象的行为
C、规则与函数申明规则相同,受到访问修饰符的影响
D、返回值参数不作限制、方法数量不做限制
【注1】成员方法不加static关键字
【注2】成员方法必须实例化出对象,再通过对象来使用,相当于该对象执行了某种行为
(2)成员方法(函数)的使用
也可将某类作为参数传入另一个类的方法中,建立两个类之间的联系:
4.构造函数、析构函数和垃圾回收机制
(1)构造函数
基本概念:在实例化对象时会调用的,用于初始化的函数,如果不写,默认存在一个无参构造函数(类中允许自己申明无参构造函数,结构体中必须有参)
写法:没有返回值(void都没有);函数名与类名必须相同;没有特殊需求时一般都是public;构造函数可以被重载;this代表当前调用该函数的对象自己
【注】若不申明构造函数的话,默认会有一个无参构造函数,不过一旦申明了有参的构造函数,原先默认的无参的构造函数就会被顶掉,此时再直接new 类名() 就会报错,但如果又自己申明了一个无参的构造函数,那该句就不会报错了,成员变量初始值就会是无参构造函数里定义的初始值
(3)析构函数(了解即可)
基本概念:当引用类型的堆内存被回收时,会调用该函数
基本语法:~类名(){}
【注】c#中存在自动垃圾回收机制GC,所以基本不会使用析构函数,除非想在某个对象被垃圾回收时做一些特殊处理
(4)垃圾回收机制
什么是垃圾:
检测所有对象,当一个对象没有被任何变量持有,它就是垃圾,在检测完之后,所有垃圾都会被回收,释放内存。
为什么会有垃圾回收:
为了避免内存溢出,GC会定期执行垃圾回收,回收分配给没有 有效引用 的对象的内存。
GC负责的范围:
GC只负责堆(Heap)内存的垃圾回收,栈(Stack)上的内存是由系统自动管理的
触发垃圾回收的时机:
A、系统每隔一段时间就会自动触发(每个平台上间隔时间不一样)
B、当系统内存不足的时候就会触发
C、可以调用Unity中的API手动触发(GC.Collect();)
为什么要减少垃圾(GC带来的问题):
垃圾回收的执行者是CPU,当垃圾越多,GC就会越频繁,CPU负担越大,内存越碎片化,GC也就会更频繁地被触发
如何减少垃圾(避免垃圾回收):
A、减少new的使用,并且缓存有必要的变量(比如尽量不要在update中new对象)
B、使用对象池技术,尽量提高每一个对象的复用率
C、使用公用的对象(即静态成员static,但要谨慎使用)
D、避免装箱拆箱的操作
E、其他一些小技巧,比如将String换成StringBuilder拼接字符串,string容易导致内存泄露
手动触发垃圾回收:
GC.Collect();
垃圾回收时会造成游戏卡顿,所以一般不会频繁调用,一般都是在内存满了或是loading过场景时使用函数进行垃圾回收
5.成员属性
(1)作用
作用:A、用于保护成员变量;B、为成员属性的获取和赋值添加逻辑处理;C、解决3p的局限性—— 让成员变量在外部 只能获取不能修改,或是 只能修改不能获取
(2)基本语法
//访问修饰符 属性类型 属性名
//{
// get{}
// set{}
//}
【注1】get和set可以只有一个(一般是只有get,就是只能得不能改)
【注2】get和set前可以加访问修饰符(默认不加,会使用属性声明时的访问权限,并且加的访问修饰符权限要低于属性的访问修饰符权限,但不能让get和set的访问权限都低于属性的权限)
实例:
(3)自动属性
如果类中有一个特征是只希望外部能得不能改的,又没什么特殊处理,那么就可以直接使用自动属性对其进行包装
public float Height
{
//没有在get和set中写逻辑的需求或想法
get;
set;
}
6.索引器
(1)基本概念和语法
基本概念:让对象像数组一样通过索引器访问其中的元素,使程序简洁美观
语法:
//访问修饰符 返回值 this[参数类型 参数名, 参数类型 参数名]
//{
// 内部的写法和规则 与 索引器相同
// get{}
// set{}
//}
实例:
(2)索引器中可以写逻辑
(3)索引器重载(可以有多个索引器)
7.静态成员
(1)基本概念
用static关键字修饰的 成员变量、方法、属性等就称为静态成员 。
静态成员的特点:用 类名点出来直接使用,不需要实例化
(2)自定义静态成员
定义:
使用:(静态成员不用实例化,直接类名点出来即可)
(3)使用静态成员的注意点
【注1】static和访问修饰符的顺序无所谓
【注2】为什么静态成员可以不经过实例化直接点出来就能使用呢:
我们要使用的对象都是要在内存中分配内存空间的,之所以要实例化对象,目的就是分配内存空间,在程序中产生一个抽象的对象。
而静态成员的特点就是在程序开始运行时就会分配内存空间,并且直到程序结束时内存空间才会被释放。
所以一个静态成员就会有自己唯一一个的“内存小房间”(即 静态存储区)。
这让静态成员有了唯一性,我们在任何地方都是用的小房间里的内容,就也能直接点出来使用了。
这也是不经常使用静态的原因:静态存储区占用内存太大,又无法GC,动态内存占比小,GC太过频繁会造成程序卡顿
【注3】静态函数中不能使用非静态成员 :非静态成员变量必须要实例化出来后才能使用,不能无中生有,非要在静态函数中使用的话,就要在函数中实例化
【注4】非静态函数可以使用静态成员
(4)静态成员的作用
A、常用唯一变量的声明 / 常用唯一的方法声明(比如相同规则的数学计算函数)
B、方便别人获取的对象声明
(5)静态成员和常量的区别
const(常量)可以理解为特殊的static(静态),他们的相同点在于都可以通过类名点出直接使用,区别主要在于:
A、const必须初始化,不能修改,static没有这个限制
B、const只能修饰变量,static可以修饰变量、方法、属性等很多
C、const一定是写在访问修饰符后面的,static没有这个要求
(6)例题(单例模式初探)
请实现一个类对象,在整个应用程序的生命周期中,有且仅会有一个该对象存在,不能在外部实例化,直接通过该类的类名就能够得到唯一的对象:
8.静态类和静态构造函数(方法)
(1)静态类
概念:用static修饰的类,只能包含静态成员,不能被实例化
作用:将常用的静态成员写在该类中,方便使用;静态类不能被实例化更能体现工具类的唯一性。比如console就是一个静态类
(2)静态构造函数
概念:用static修饰的构造函数
特点:
A、静态类和普通类都可以有
B、不能使用访问修饰符
C、不能有修改
D、只会自动调用一次
作用:在静态构造函数中初始化静态变量
使用:
9.拓展方法
(1)基本概念
基本概念:为现有非静态 变量类型 添加新方法
作用:
A、提升程序拓展性;
B、不需要再在对象中重新写方法;
C、不需要继承来添加方法;
D、为别人封装的类型写额外的方法
特点:
A、一定写在静态类中
B、一定是个静态函数
C、第一个参数为拓展目标
D、第一个参数用this修饰
(2)语法
//访问修饰符 static 返回值 函数名(this 拓展类名 参数名,参数类型 参数名,参数类型 参数名,...)
(3)实例(为自定义的类拓展方法)
【注】若拓展方法名与原有方法名重名了的话,那能用的是原方法,相当于拓展方法没有用了
10.运算符重载
(1)基本概念
基本概念:让自定义类和结构体能够使用运算符。
关键字: operator
特点:一定是一个公共的静态方法,返回值要写在operator前,逻辑处理自定义
注意点:条件运算符需要成对实现;一个符号可以多个重载;不能使用ref和out
(2)实例
11.内部类和分部类
(1)内部类基本概念
基本概念:在一个类中再申明一个类。在使用时要用包裹着点出自己。
【注】默认内部类是私有的,要想在外部访问要用public。
(2)分部类基本概念
基本概念:把一个类分成几部分申明。作用在于:分部描述一个类,增加程序的可拓展性
关键字:partial
(3)分部方法
基本概念:将方法的声明和实现分离
特点:不能加访问修饰符,默认私有;只能在分部类中声明;返回值只能是void;可以有参数但不能用out关键字
(二)继承
继承是指允许新创建的类继承现有类的属性和方法,这样可以提高代码的复用率和可扩展性。
1.继承的基本规则
(1)基本概念
一个类A继承类B,类A将会继承类B的所有成员,A类将拥有类B的所有特征和行为。子类只能有一个父亲;子类可以间接继承父类的父类。
(2)语法
class 类名:被继承的类名
(3)访问修饰符的影响——protected
父类用了private, 子类中虽也包含这个成员,但不能直接使用。但父类使用protected的话,外部不允许访问,但子类是可以使用的 。
【注】极不建议父类和子类使用相同名称的成员,非要使用要加上new关键字
2.里氏替换原则
(1)基本概念
基本概念:任何父类出现的地方,子类都可以替代;
语法表现:父类容器装子类对象,因为子类对象包含了父类所有内容
作用:方便进行对象的存储和管理。通常会用到数组+is+as,用一个基类容器来存储场景中的各种对象,然后通过条件判断来决定各个对象应该处理什么样的逻辑。
(2)实现
但此时player等对象是不能用Player类中特有的方法的,需要用is+as来进行转换
(3)is 和 as
is:判断一个对象是否是指定类对象。返回值是bool类型
as:将一个对象转换为指定类对象。转换成功则返回值是指定类型对象,失败则返回null
基本语法:
//类对象 is 类名
//类对象 as 类名
【注】一般is 和 as会配合使用:(对于数组里氏替换原则很好用)
3.继承中的构造函数
(1)基本概念
在申明一个子类对象时,会先调用父类的构造函数,再调用子类的构造函数
(2)父类的无参构造函数很重要
子类实例化时默认调用的是父类的无参构造函数,所以如果父类的无参构造函数被顶掉了的话(构造函数的重载),会报错
(3)base关键字
子类可以通过base关键字 (base就指代父亲) 调用父类含有某个参数的构造函数。不想实例化时调用默认的父类的无参构造函数,而是指定调用某一个有参构造函数,就要用base关键字
4.万物之父和装箱拆箱
(1)基本概念
基本概念:object是所有类型的父类,它是一个类(引用类型)。
作用:可以利用里氏替换原则,用object容器装所有对象;可用来表示不确定类型,作为函数参数类型
(2)装箱和拆箱
发生条件:用object存值类型就称为装箱,再把object转为值类型就称为拆箱。
进一步理解:装箱的过程,就是把值类型用引用类型来存储,栈内存会迁移到堆内存中;拆箱的过程,就是把引用类型存储的值类型取出来,堆内存会迁移到栈内存中。
好处:不确定类型时可以方便参数的存储和传递。
坏处:存在内存迁移,增加了性能消耗
举例:
5.密封类
(1)基本概念
用sealed密封关键字修饰的类,其作用是让该类无法再被继承
(2)作用
密封类主要作用就是不允许最底层子类被继承,这样可以保证程序的规范性和安全性
(三)多态
多态是指允许不同的对象对同一消息作出不同的响应,这个概念主要体现在父类和子类、接口的多种实现中。应用场景比如说角色的行为模式的切换、碰撞检测、事件处理等等.
1.虚方法
之前学到的函数重载就是多态的其中一个表现,现在学习的是虚方法:用virtual关键字修饰的函数是虚函数,可以被用override关键字修饰的子类重写
2.抽象类和抽象方法
(1)基本概念
基本概念:用关键字abstract修饰的类。其特点是:
A、不能被实例化,但符合里氏替代原则,用抽象类容器装子类对象;
B、可以包含抽象方法;
C、继承抽象类必须重写其抽象方法
作用:父类比较抽象不希望被实例化,其中的行为不太需要被实现的,只希望子类去定义具体的规则的,可以选择抽象类然后使用其中的抽象方法来定义规则
(2)抽象函数(纯虚方法)
用abstract关键字修饰的方法
特点:
A、只能在抽象类中申明
B、没有方法体
C、不能是私有的
D、继承后必须实现,用override重写
(3)虚方法和抽象方法的联系与区别
1、虚方法在任意类中都能写,但抽象方法只能写在抽象类中
2、虚方法可由子类选择性实现,但抽象方法在子类中必须要实现
3、虚方法和抽象方法都可以被无限重写(子类的子类的子类…都可以重写该方法)
3.接口
(1)基本概念与规范
基本概念:接口是行为的抽象规范,也是一种自定义的类型。
关键字:interface
特点:和类的申明类似,接口是用来继承的,不能被实例化,但是可以作为容器存储对象
接口申明的规范:
1、不包含成员变量
2、只包含方法、属性、索引器、事件
3、成员不能被实现
4、成员可以不用写访问修饰符,不能是私有的
5、接口不能继承类,但是可以继承另一个接口
接口使用的规范:
1、类可以继承多个接口
2、类继承接口后,必须实现接口中所有成员
(2)声明语法
//interface 接口名
//{
//}
【注】接口名的命名规范一般是在帕斯卡命名前加一个I
实例:
(3)显式实现接口
当一个类继承2个接口,但是接口中存在同名的方法时,需要显示实现接口,就是用接口名.行为名来实现,但是在实现方法时需要先用as转为指定的接口类型才能调用方法
【注】显示实现接口时,不能写访问修饰符
(4)抽象类和接口的区别
相同点:
A、都可以被继承;
B、都不能被实例化;
C、都可以包含方法的申明;
D、子类必须实现未实现的方法;
E、都遵循里氏替换原则
区别:
A、抽象类中可以有构造函数,接口中不能;
B、抽象类只能被单一继承,接口可以被多个继承;
C、抽象类中可以有成员变量,接口不能;
D、抽象类中可以申明成员方法、虚方法、抽象方法、静态方法,而接口中只能申明没有实现的方法;
E、抽象类方法可以使用访问修饰符,接口中建议不写,默认为public
如何选择抽象类和抽象方法:
表示对象的用抽象类,表示行为拓展的用接口,不同对象拥有共同的行为,往往可以用接口来实现(例如:动物是一类对象,自然会选择抽象类,而飞翔是一个行为,自然会选择接口)
4.密封方法
基本概念:用sealed修饰的重写函数,和override一起出现
作用:让虚方法或者抽象方法不能再被重写
三、面向对象编程七大原则
1.七大原则要实现的目标
目标:高内聚、低耦合,使程序模块的可重用性、移植性增强。
高内聚低耦合:从类的角度来看,就是减少类内部 对其他类的调用;从功能模块上来看,就是减少模块(功能,如UI功能、战斗功能等)之间的交互复杂度
2.七大原则概念
四、面向对象的其他相关知识
1.命名空间
基本概念:命名空间是用来组织代码的,就像是一个工具包,类就像是一件件工具,都是声明在命名空间中的
使用关键字:using
【注1】不同命名空间中相互使用,需要引用命名空间或指明出处
【注2】命名空间可以重名,但重名的命名空间下类的名称不能相同。在不同名的命名空间中是可以有同名类的,但此时要点出出处(命名空间.类名)
【注3】命名空间可以包裹命名空间(工具包中的小工具包),同理注意用点来指明出处
2.万物之父中的方法
(1)object中的静态方法:Equals 和 ReferenceEquals
静态方法Equals :用来判断两个对象是否相等,最终的判断圈交给左侧对象的Equals方法(不管值类型还是引用类型都按照左边对象的Equals方法的规则进行比较)
静态方法ReferenceEquals :用来比较两个对象是否是相同的引用,主要用来比较引用类型对象,值类型对象返回值始终是false
(2)object中的成员方法:GetType 和 MemberwiseClone
GetType:获取对象运行时的类型Type,通过Type结合后续反射相关知识点可以做很多关于对象的操作
MemberwiseClone:用于获取对象的浅拷贝对象,即返回一个新的对象,新对象中的引用变量会和老对象一致
3.String中的方法
//字符串指定位置获取
string str="xiaoliang";
Console.WriteLine(str[0]);
//字符串拼接:Format
str=string.Format("{0}{1}",1,2222);
//正向查找字符串所在的位置(从0开始,没找到默认返回-1):IndexOf
string str="xiaoliang";
int index=str.IndexOf("a")
Console.WriteLine(index);
//反向查找字符串所在的位置(可以查找一组词):LastIndexOf
string str=我是小梁小梁";
int index=str.LastIndexOf("小梁");
Console.WriteLine(index);
//移除指定位置及这之后的所有字符:Remove
//【注1】string修改后是会创建一个新的对象,原来的对象并不会跟着变,所以要赋值给一个对象
string str="xiaoliang";
str.Remove(3);
Console.WriteLine(str);
//【注2】传入2个参数的话可以在中间指定位置截取(包含指定的那个位置的字符):
//参数1:开始位置 参数2:字符个数
str=str.Remove(1,1);
//替换指定字符串:Replace
string str="xiaoliang";
str.Replace("liang","hong");
//大小转换:ToUpper ToLower
string str="xiaoliang";
str.ToUpper();
//字符串截取(包含指定位置字符串):Substring
string str="xiaoliang";
str.Substring(2);
//【注】也可以传入两个参数,截取中间一节,不会自动的帮助判断是否越界
//字符串切割:Split
string str="1,2,3,4,5,6";
string[] strs=str.Splite(',');
4.stringBuilder
string是很特殊的引用,每次重新赋值或者拼接时会重新分配新的内存空间,所以如果一个字符串经常改变会非常浪费空间,而StringBuilder就是来解决这个问题的。
(1)基本概念
StringBuilder是c#提供的一个处理字符串的公共类,主要解决的问题是:修改字符串而不创建新的对象,需要频繁修改和拼接的字符串可以用它,可以提升性能,优化内存
【注1】在使用前需要引用命名空间:System.Text
【注2】在扩容时还是会开房间从而产生垃圾,但只要容量够,在改变字符时就不会重新开房间
(2)增、拼接、插入、删、查、改、替换
文章来源:https://www.toymoban.com/news/detail-831275.html
(3)string 和 stringBuilder的区别
string每次修改拼接时会重新分配内存空间,产生垃圾,而stringBuilder修改字符串时不会创建新的对象,因此需要频繁修改和拼接的字符串可以用stringBuilder,可以提升性能。但string提供了更多方法供使用,需要使用这些特殊方法来处理一些特殊逻辑时可以使用string。文章来源地址https://www.toymoban.com/news/detail-831275.html
到了这里,关于Unity学习笔记(零基础到就业)|Chapter03:C#核心的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!