赞
踩
内容将会持续更新,有错误的地方欢迎指正,谢谢!
拥有更好的学习体验 —— 不断努力,不断进步,不断探索 |
助力快速掌握 面试题 为面试者节省宝贵的学习时间,避免困惑! |
存储位置不同:
值类型的数据存储在栈中(值类型嵌套在引用类型里时,跟随引用类型存储),而引用类型的数据存储在堆中,并且在栈中存储堆内存数据的地址。
效率不同:
值类型不需要地址转换,存取快,引用类型需要进行地址转换,因为引用类型的变量存储的的是堆中的实例的地址,存取慢。
内存分配时机不同:
值类型声明变量后,不管是否已经赋值,编译器都会为其分配内存,引用类型声明变量后,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其在堆上分配内存空间,当创建一个实例时,会分配堆上的空间,并把堆上的空间的地址保存到栈上分配的小片空间中。
释放方式不同:
存储在栈中的值类型在使用完成后会自动释放,而存储在堆上的引用类型则靠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、将值类型变量的值复制到堆上新分配的内存空间上
3、将堆上创建的对象的地址返回给引用类型变量
拆箱过程:
1、检查已装箱的值类型,确保它是给定值类型的一个装箱值
2、将堆中存储的值拷贝到栈上的值类型实例中
(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在拿到参数的时候就不用再担心内部再次装箱和拆箱了。 }
使用ref型参数时,传入的参数必须先被初始化,对out型参数时,不需要先被初始化,但是在方法中需要对其完成初始化。
使用ref和out时,在方法的参数和执行方法时,都要加ref或out关键字,以满足匹配。
out参数适用于需要返回多个返回值的时候,而ref参数则适用于需要在方法中修改调用方引用参数的引用的对象。
out参数和ref参数它们都是按引用传递,而不是按值传递。
ref和out关键字都让形参成为了实参的别名,所以在传递参数时必须是变量,而不能是表达式。
抽象类是对现实世界对象的抽象化表示,通常仅定义它具有的特征,功能、关系,但不去做具体的实现,即为抽象类。
抽象类特征:
接口就像一种行为规范,是为后续代码编写与程序开发制定的一种协定。
接口类特征:
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;
}
}
相同点:
不同点:
一个类继承了某个抽象类表示它“是什么”,实现了某个接口表示它“有什么功能”或者会做什么事,但不表达“如何做”
比如:燕子(具体类)是鸟(抽象类),会飞(接口)。C#中不支持多继承,即燕子只能是鸟,不会是其他东西了;但可以有多个功能,做很多事,比如会飞(IFly),会吃(IEat)。
每一次跌倒都是一次成长 每一次努力都是一次进步 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。