内容将会持续更新,有错误的地方欢迎指正,谢谢!
拥有更好的学习体验 —— 不断努力,不断进步,不断探索 |
助力快速掌握 面试题 为面试者节省宝贵的学习时间,避免困惑! |
一、请简述值类型与引用类型的区别
-
存储位置不同:
值类型的数据存储在栈中(值类型嵌套在引用类型里时,跟随引用类型存储),而引用类型的数据存储在堆中,并且在栈中存储堆内存数据的地址。 -
效率不同:
值类型不需要地址转换,存取快,引用类型需要进行地址转换,因为引用类型的变量存储的的是堆中的实例的地址,存取慢。 -
内存分配时机不同:
值类型声明变量后,不管是否已经赋值,编译器都会为其分配内存,引用类型声明变量后,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其在堆上分配内存空间,当创建一个实例时,会分配堆上的空间,并把堆上的空间的地址保存到栈上分配的小片空间中。 -
释放方式不同:
存储在栈中的值类型在使用完成后会自动释放,而存储在堆上的引用类型则靠GC进行垃圾回收。 -
继承不同:
值类型隐式派生自System.ValueType,引用类型继承自System.Object。 -
默认值不同:
值类型不能为null,默认初始化为该类型的默认值,引用类型默认初始化为null。 -
参数传递上的不同:
值类型作为参数传递时,传递的是数据,引用类型作为参数传递时传递的是数据的引用。 -
赋值上的不同:
当一个变量赋值给另一个变量时,值类型是会复制一个相同的新的值给另一个变量,引用类型是会将原变量数据的引用给新变量,当改变新对象的数据时也会对原对象照成影响,因为他们的引用是同一个。 -
值类型:
struct、enum、bool、char、byte、sbyte、short、ushort、int、uint、long、ulong、float、double (6类:结构体、枚举、布尔、字符、整型、浮点型) 。 -
引用类型:
class、interface、delegate、string、object、array、list(6类:自定义类,接口、委托、字符串、对象、数组)
二、什么是装箱和拆箱
1、什么是装箱拆箱
装箱就是值类型到引用类型的隐式转换,拆箱就是引用类型到值类型的显示转换。
2、装箱和拆箱的过程
-
装箱过程:
1、在堆上分配一个内存空间,这些内存空间主要用于存储值类型的数据
2、将值类型变量的值复制到堆上新分配的内存空间上
3、将堆上创建的对象的地址返回给引用类型变量 -
拆箱过程:
1、检查已装箱的值类型,确保它是给定值类型的一个装箱值
2、将堆中存储的值拷贝到栈上的值类型实例中
3、 如何避免频繁的装箱拆箱
(1)、通过重载函数:Struct通过重载函数来避免装箱拆箱
struct Test
{
int a;
public override string ToString()
{
return "a的值是:" + a;
}
//如果struct没有重载ToString()方法,就会在struct实例调用它们时先装箱再调用,导致内存块重新分配性能损耗
}
(2)、通过泛型
interface ITest
{
void Change(int a);
}
struct A : ITest
{
private int aa;
public A(int aa)
{
this.aa = aa;
}
public void Change(int aa)
{
this.aa = aa;
}
}
struct B : ITest
{
private int bb;
public B(int bb)
{
this.bb = bb;
}
public void Change(int bb)
{
this.bb = bb;
}
}
private void TestFunction<T>(T itest) where T:ITest
{
itest.Change(100);
}
private void Start()
{
B b = new B(10);
TestFunction(b);
//A和B都继承了ITest接口,当在实例化A或B的时候已经提前做了装箱操作,TestFunction在拿到参数的时候就不用再担心内部再次装箱和拆箱了。
}
(3)、通过统一的接口提前装箱拆箱
interface ITest
{
void Change(int a);
}
struct A : ITest
{
private int aa;
public A(int aa)
{
this.aa = aa;
}
public void Change(int aa)
{
this.aa = aa;
}
}
struct B : ITest
{
private int bb;
public B(int bb)
{
this.bb = bb;
}
public void Change(int bb)
{
this.bb = bb;
}
}
private void TestFunction(ITest itest)
{
itest.Change(100);
}
private void Start()
{
ITest test = new B(10);
TestFunction(test);
//A和B都继承了ITest接口,当在实例化A或B的时候已经提前做了装箱操作,TestFunction在拿到参数的时候就不用再担心内部再次装箱和拆箱了。
}
4、装箱拆箱缺点
- 由于装箱时生成的是全新的对象,会不断的分配和销毁内存,不但会大量消耗CPU,也同时增加了内存碎片,降低了性能。
- 装箱和拆箱都会导致数据在堆和栈上进行拷贝,频繁的装箱拆箱操作会性能损失。
5、装箱使用情形
- 调用含类型为Object的参数的方法时,该Object可支持任意类型,以便通用,当传入值类型的值就需要进行装箱。
- 对于一个非泛型容器,同样为了保证通用,而将元素类型定义为Object,当值类型数据添加进容器的时候就需要装箱。
三、ref引用参数和out输出参数的区别?
-
使用ref型参数时,传入的参数必须先被初始化,对out型参数时,不需要先被初始化,但是在方法中需要对其完成初始化。
-
使用ref和out时,在方法的参数和执行方法时,都要加ref或out关键字,以满足匹配。
-
out参数适用于需要返回多个返回值的时候,而ref参数则适用于需要在方法中修改调用方引用参数的引用的对象。
-
out参数和ref参数它们都是按引用传递,而不是按值传递。
-
ref和out关键字都让形参成为了实参的别名,所以在传递参数时必须是变量,而不能是表达式。
四、class与struct区别?
-
存储位置
struct结构体是值类型,存储在栈上,class类是引用类型,对象存储在堆上,引用存储在栈上,结构体在声明的时候已经为其在栈上分配了内存,而类需要在用new为其分配堆上的内存。 -
新建对象
class的对象需要通过new关键字创建,而struct可以用也可以不用new关键字来创建,当不用new关键字来创建时,用户不允许访问其方法、事件和属性。 -
构造函数
struct不能定义无参构造函数,因为结构体的无参构造函数是自动定义且不能改变的,默认的无参构造函数会一直存在,默认无参构造函数不会因为定义了有参构造函数就消失,class默认有一个无参构造函数,当我们显示声明了一个构造函数时原来的默认构造函数没有了。struct可以自定义有参构造函数,但是需要在构造函数中初始化所有成员变量,而类不需要。 -
析构函数
struct不能有析构函数,class可以有析构函数 -
继承
struct不能从另一个struct或class继承,也不能作为struct和class的基类,而class可以继承其他的class,也可以作为其他类的基类,结构体和类都可以实现一个或多个接口。 -
默认访问访问符
如果未指定访问指示符,对class而言,其成员默认是private,而struct而言,默认public。 -
初始值
struct成员变量声明不能指定初始值,class可以指定初始值。 -
修饰符
结构体类型是隐式密闭的,不能使用sealed和abstract修饰。结构体的成员不能被指定为抽象的、虚拟的或者保护的对象,因此结构体的成员不能使用abstract、virtual和protected修饰符,但是函数可以使用override关键字用于覆盖基类中的方法。
五、数组、ArrayList、List的主要区别?
- 数据的容量是固定的,当我们在实例化数组时必须指定长度,数组的空间是连续的,插入和删除元素效率低,而ArrayList或List的容量可根据需要自动扩充、修改、删除或插入数据。
- 数据可以是多个维度的,而ArraList和List只有一个维度。
- ArrayList中存储的对象是Object类型,在存取的时候需要经过装箱拆箱处理,会带来很大的性能消耗,同时也会造成数据不安全的问题隐患。
-
在声明List集合时需要声明集合内的数据类型,只能存储指定的类型。
六、private、public、protected、internal、sealed区别?
- private 是完全私有的,只有在类自己里面可以调用,在类的外部和子类都不能调用,子类也不能继承父类的private的属性和方法。
- public 对任何类和成员完全公开,无访问限制。
- protected 只有自己和自己的子类可以调用,子类可以继承父类的protected的属性和方法。
- internal同一应用程序集内部可以访问。
- sealed声明类就不能继承,声明方法就不能被重写。
-
访问权限private<protected<internall<public。
七、描述Interface与抽象类的区别?
1、什么是抽象类
抽象类是对现实世界对象的抽象化表示,通常仅定义它具有的特征,功能、关系,但不去做具体的实现,即为抽象类。
抽象类特征:
- 抽象方法只能出现在抽象类中,且不包含任何实现,但是抽象类中可以包含普通方法。
- 抽象类不能实例化,只能实例化它的派生类。
- 抽象类和抽象方法需要使用abstract关键字。
- 子类实现父类的抽象方法时,需要添加override关键字。
- 抽象类可以派生自一个抽象类,可以重写基类的抽象方法,也可以不重写,如果不重写的话则其派生的普通类必须重写他们。
2、什么是接口
接口就像一种行为规范,是为后续代码编写与程序开发制定的一种协定。
接口类特征:
- 接口的成员不能有任何的实现
- 接口不能被实例化
- 接口的成员不能有访问修饰符,默认为public
- 接口可以派生自一个接口,可以实现父接口中的方法,也可以不实现,如果实现的话,其实现接口的子类必须将接口的所有成员函数实现
- 子类实现接口的方法时,不需要任何关键字,直接实现即可
- 接口中只能包含属性、方法、索引器和事件
- 一个类可以实现多个接口,且每个接口中所有的成员必须都实现
public delegate void DelegateTest();
public interface ITest //只能包含非静态成员函数,隐式public,但不允许访问修饰符
{
void Method(string a); //方法
string Property //属性
{
get; set;
}
event DelegateTest EventTest; //事件,需要先定义一个委托
int this[int index] //索引器
{
get; set;
}
}
3、抽象类和接口的区别
相同点:
- 都不能实例化
- 包含未实现的方法声明
- 派生类必须实现未实现的方法,抽象类是抽象方法,接口则是所有成员
不同点:文章来源:https://www.toymoban.com/news/detail-851450.html
- 接口用于规范,用于为不相关的类提供通用的功能,抽象类用于共性,用于关系密切的对象
- 抽象类是类,故只能单继承,接口可一次实现多继承
- 接口中只能声明方法、属性、事件、索引器,不能定义字段,不能实现方法,抽象类则可以,它是一个不完全类,抽象类里面有抽象的方法,属性,也可以有具体的方法和属性
- 接口成员必须是公共的,且不写public,抽象类中的非抽象方法成员访问权限任意,但是抽象方法权限不能是private
- 接口可作用于值类型和引用类型,抽象类只能作用于引用类型,struct可以实现接口,不能继承类
一个类继承了某个抽象类表示它“是什么”,实现了某个接口表示它“有什么功能”或者会做什么事,但不表达“如何做”
比如:燕子(具体类)是鸟(抽象类),会飞(接口)。C#中不支持多继承,即燕子只能是鸟,不会是其他东西了;但可以有多个功能,做很多事,比如会飞(IFly),会吃(IEat)。文章来源地址https://www.toymoban.com/news/detail-851450.html
每一次跌倒都是一次成长 每一次努力都是一次进步 |
如果您喜欢本博客,请点赞和分享给更多的朋友,让更多人受益。同时,您也可以关注我的博客,以便及时获取最新的更新和文章。
在未来的写作中,我将继续努力,分享更多有趣、实用的内容。再次感谢大家的支持和鼓励,期待与您在下一篇博客再见!
到了这里,关于Unity之C#面试题(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!