【Unity面试】 C#语言基础核心 | 面试真题 | 全面总结 | 建议收藏

你知道的越多,你不知道的越多 🇨🇳🇨🇳🇨🇳
点赞再看,养成习惯,别忘了一键三连哦 👍👍👍
文章持续更新中 📝📝📝



1️⃣你知道面向对象的三大特点吗?🔥🔥🔥🔥🔥

🔑关键点:面向对象有三大特点:封装、继承、多态

封装:

  1. 从数据角度讲,将一些基本数据类型复合成一个自定义类型,数据封装,外部不可以直接读写。
  2. 从方法角度讲,向类外提供功能(调用接口),类内隐藏实现的细节。
  3. 从设计角度讲,分而治之,高内聚低耦合,封装变化。

属性器值的读写,get和set访问器,是封装的最好的体现!!!

继承:

  1. 统一概念,层次化管理代码功能
  2. 重用现有类的功能,可以实现代码的复用性,在此基础上进行扩展,增强嘞可维护性,并且符合开闭原则。

通过类实现单继承,接口来实现多继承。

多态:

  1. 父类的同一种动作或者行为,在不同的子类上有不同的实现。
    (父类调用同一方法,在不同的子类上有不同的执行效果)
  2. 2种多态:编译时多态:方法重载、运行时多态:重写虚成员

实现手段:虚方法、抽象方法、接口方法、方法重载,运算符重载,重写override。

图例帮助记忆
在这里插入图片描述


2️⃣值类型和引用类型有什么区别吗?🔥🔥🔥

  1. 🔑关键点:你知道常用的值类型和引用类型
    值类型:包含了所有简单类型(整数、浮点、bool、char)、struct、enum。
    继承自System.ValueTyoe
    引用类型包含了string,object,class,interface,delegate,array
    继承自System.Object
  2. 🔑关键点:内存区域上的区别
    值类型:数据存储在栈上,超出作用域就自动清理
    引用类型:数据存储在托管堆上,引用地址在线程栈上,地址指向数据存放的堆上
    托管堆会由GC来自动释放 ,线程栈数据在作用域结束后会被清理。

3️⃣值类型和引用类型, 值类型的成员中包含引用类型该成员的值放在哪,引用类型放在哪。引用类型中包含值类型,值类型包含在哪。🔥🔥🔥🔥

  1. 引用类型一定在堆里
  2. 值类型如果作为引用类型的成员,那么会在堆里,如果值类型声明在方法里,那就是在栈里。

通常来讲 变量的值分配 与其声明该变量的位置有关。 局部变量的值 总是在 栈上的。 实例变量的值则和实例本身一起储存在实例储存的地方。 引用类型实例和静态总是储存在 堆上的。
数组的元素、引用类型中的值类型字段等,引用类型的确总是分配在托管堆上, 但是值类型并非总是分配在线程栈上有可能分配在堆上。


4️⃣装箱和拆箱的区别吗?🔥🔥🔥🔥

值类型和引用类型的最终基类是Object
装箱:值类型转换成引用类型的过程,生成新的引用类型
拆箱;引用类型转换成值类型的过程

装箱操作:托管堆分配内存,值类型拷贝数据,object地址指向托管堆对象,产生性能消耗
拆箱操作:根据object引用地址找到托管堆上的数据,栈上数据拷贝

如何避免装箱操作,减少内存请求和分配,提升性能呢
解决办法就是第一是重载,第二是泛型


5️⃣请描述interface和抽象类之间的不同?🔥🔥

  1. 接口和抽象类都不能被实例化,但是抽象类的子类对象可以实例化。
  2. 接口interface可以定义方法、属性、索引器、事件
  3. 抽象类abstract可以定义字段、静态字段和方法、抽象方法、属性、构造函数
  4. 接口可以继承多个接口,抽象类只能继承一个类
  5. 接口的继承必须实现所有成员,抽象类重写override抽象方法
  6. 抽象类可以派生自另一个抽象类,接口可以多重实现,抽象类只能单一继承
  7. 抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果
  8. 抽象类实现了 OOP 中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的交给子类去实现。

举个例子:抽象类门,多接口继承【可破坏、金属】的行为方法,派生类实例化这个门,接口的实现类实现具体行为,派生类创建这样具体的可破坏的铁门

图例帮助记忆
在这里插入图片描述


6️⃣public、private、protected、internal、sealed的区别🔥

🔑public全局、private类内部、protected派生类、internal本程序集
🔑sealed声明类就不能继承,声明方法是不能被重写,密封类+密封函数


7️⃣HR:foreach迭代器遍历和for循环遍历的区别🔥🔥🔥

⁉️如果集合需要foreach遍历,是否可行,存在一定问题
🔑foreach中的迭代变量item是的只读,不能对其进行修改
❌错误示例:比如在foreach循环中直接对List.Remove(item)操作,是错误的
⭕️解决办法:foreach只读的时候,用变量记录下来,在对记录做操作,或者直接用for循环遍历
foreach对int[]数组循环已经不产生GC,但是要避免对ArrayList进行遍历

🔑for语句中初始化变量i的作用域,循环体内部可见。
🔑通过索引进行遍历,可以根据索引对所遍历集合进行修改
unity中for循环使用lambda表达式注意闭包问题,解决办法是对变量i进行缓存

🚩Foreach遍历原理:迭代器
任何集合类(Array)对象都有一个GetEnumerator()方法,该方法可以返回一个实现了 IEnumerator接口的对象。
这个返回的IEnumerator对象既不是集合类对象,也不是集合的元素类对象,它是一个独立的类对象。
通过这个实现了 IEnumerator接口对象A,可以遍历访问集合类对象中的每一个元素对象
对象A访问MoveNext方法,方法为真,就可以访问Current方法,读取到集合的元素。


8️⃣C#中有哪些常用的容器类,各有什么特点,性能区别?🔥🔥🔥🔥🔥

🚩容器类:Stack、Queue、Array、ArrayList、List、LinkList、Directionary、HashSet、HashTable

  1. Stack栈:先进后出,入栈和出栈,底层泛型数组实现,入栈动态扩容2倍

  2. Queue队列:先进先出,入队和出队,底层泛型数组实现,表头表尾指针,判空还是满通过size比较
    Queue和Stack主要是用来存储临时信息的

  3. Array数组:数组需要声明长度,不安全

  4. ArrayList数组列表:动态增加数组,不安全,实现了IList接口(表示可按照索引进行访问的非泛型集合对象),Object数组实现

  5. List<T>列表:底层实现是泛型数组,特性,动态扩容,泛型安全
    将泛型数据(对值类型来说就是数据本身,对引用类型来说就是引用)存储在一个泛型数组中,添加元素时若超过当前泛型数组容量,则以2倍扩容,进而实现List大小动态可变。(注:大小指容量,不是Count)

  6. LinkList链表
    1、数组和List、ArrayList集合都有一个重大的缺陷,就是从数组的中间位置删除或插入一个元素需要代价,其原因是数组中处于被删除元素之后的所有元素都要向数组的前端移动,导致性能消耗
    2、LinkedList(底层是由链表实现的)基于链表的数据结构,LinkedList很好的解决了数组删除插入效率低的问题,且不用动态的扩充数组的长度。
    LinkedList的优点:插入、删除元素效率比较高;缺点:访问效率比较低。

🔑List和LinkedList的区别
List是数组列表,LinkedList是双向链表,List读取速度快,时间复杂度是O(1),增删比较麻烦,时间复杂度是O(n).
LinkedList读取时间复杂度是O(n),增删时间复杂度是O(1)
LinkedList适用于增删数据,List适用于读取数据,List适用范围好

  1. HashTable哈希表(散列表)
    概念:不定长的二进制数据通过哈希函数映射到一个较短的二进制数据集,即Key通过GetHashCode()函数获得HashCode
    装填因子:α=n/m=0.72 ,存储的数据N和空间大小M
    然后通过哈希桶算法,HashCode分段,每一段都是一个桶结构,一般是HashCode直接取余
    桶结构会加剧冲突,解决冲突使用拉链法,将产生冲突的元素建立一个单链表,并将头指针地址存储至Hash表对应桶的位置。这样定位到Hash表桶的位置后可通过遍历单链表的形式来查找元素。
    1、Key—Value形式存取,无序,类型Object,需要类型转换。
    2、Hashtable查询速度快,而添加速度相对慢
    3、Hashtable中的数据实际存储在内部的一个数据桶里(bucket结构体数组),容量固定,根据数组索引获取值。
  2. Directionary<TKey,TVaule>字典,有序,泛型存储不需要进行类型装换(不需要装箱拆箱),碰撞阈值扩容~
  3. HashSet:一组不包含重复的元素集合【LeetCode算法217存在重复元素,可直接使用HashSet解决】

🔑性能排序:
插入性能: LinkedList > Dictionary > HashTable > List
遍历性能:List > LinkedList > Dictionary > HashTable
删除性能: Dictionary > LinkedList > HashTable > List

🔑小结:
在修改较频繁,且查找和删除也较多时,首选LinkedList,
在主要以删除为主,插入为辅,且查找较少时,首选Dictionary,
在查找频繁,而又无需修改的情况下,则首选List。

注意优化Dictionary和List,提前确定容量大小,提高效率


9️⃣string、stringbuilder和stringBuffer区别?🔥🔥🔥

🚩String的特点
🔑string不变性,字符序列不可变。
🔑string对原管理中实例对象赋值,会重新开一个新的实例对象赋值,新开的实例对象会等待被GC。
🔑string拼接要重新开辟空间,因为string原值不会改变,导致GC频繁,性能消耗大

🚩StringBuffer的特点
🔑StringBuffer是字符串可变对象,可通过自带的StringBuffer方法来改变并生成想要的字符串。
🔑对原实例对象做拼接的实例,不会生成新的实例对象。

🚩StringBuilder的特点
🔑StringBuilder是字符串可变对象,基本和StringBuffer相同。

🚩StringBuilder和StringBuffer的区别
🔑唯一的区别是StringBuffer是线程安全,相关方法前带synchronized关键字,一般用于多线程
🔑StringBuilder是非线程安全,所以性能略好,一般用于单线程
🔑拼接使用StringBuilder和StringBuffer,只开辟一个内存空间,这是性能优化的点。
🔑三者性能比较 StringBuilder>StringBuffer>String

StringBuilder相关方法
StringBuilder.Append 将信息追加到当前 StringBuilder 的结尾。
StringBuilder.AppendFormat 用带格式文本替换字符串中传递的格式说明符。
StringBuilder.Insert 将字符串或对象插入到当前 StringBuilder 对象的指定索引处。
StringBuilder.Remove 从当前 StringBuilder 对象中移除指定数量的字符。
StringBuilder.Replace 替换指定索引处的指定字符。


🔟泛型是什么🔥🔥

🎬泛型要解决的问题!
多个代码对 【不同数据类型】 执行 【相同指令】的情况通过泛型来解决

🎬泛型的概念
🔑泛型:多个类型共享一组代码
🔑泛型允许类型参数化,泛型类型是类型的模板
🔑5种泛型:类、结构、接口、委托、方法
🔑类型占位符 T 来表示泛型
🔑泛型类不是实际的类,而是类的模板

🎬从泛型创建实例
🔑声明泛型类型 >>>>> 通过提供【真实类型】创建构造函数类型 >>>>>>> 从构造函数创建实例
🔑类<T1,T2> 泛型类型参数

🚩性能:泛型不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转换,所以性能得到提高
🚩安全:通过知道使用泛型定义的变量的类型限制,编译器可以在一定程度上验证类型假设,所以泛型提高了程序的类型安全。


🎁🌻🌼🌸 粉丝福利来喽 🎁🌻🌼🌸

  1. 免费领取海量资源 🎁
    简历自测评分表、Unity职级技能表、面试题库、入行学习路径等
  2. 《Unity游戏开发五天集训营 》50个名额 🎁
    我给大家争取到了 50个《游戏开发五天集训营 》名额,原价198,前50个免费
    扫码加入,暗号小听歌
    即可参加ARPG狼人战斗系统、饥荒生存类游戏开发、回合制RPG口袋妖怪游戏等游戏开发训练营
  3. 额外抽奖机会🎁
    参加游戏训练营、还有机会获得大厂老师在线面试指导、或者有机会获得价值1998元的《Unity极速入门与实战》课程
🔻🔻🔻🔻 扫下方二维码,获取游戏开发福利,暗号小听歌 🔻🔻🔻🔻
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值