.Net常见面试题整理(1)——值类型和引用类型

为了防止不提供原网址的转载,特在这里加上原文链接:http://www.cnblogs.com/zhangkai2237/archive/2013/03/17/2964528.html
         前段时间准备换个工作,所以参加了几场面试,面试的中主要考察的是C#相关知识的掌握以及实际项目中的经验。项目经验没办法整理,每个人都不一样,但是考察的基础知识我们可以归纳总结,对自己是个整理总结提高的过程。
        首先申明本人于2011年7月份毕业,目前工作约两年时间,面试的职位一般都是中级.Net工程师的职位。由于水平有限,文章中如果有错误或不到位的地方还请看到的大神指出,我一定改正。
        
        好了,回到正题。类型一直是C#中最基本的问题,关于值类型和引用类型,我想每个C#程序员都知道“值类型保存在栈上,引用类型保存在堆上”。但是仅仅知道到这里是完全不够的,我们需要理解C#中的类型,了解为什么要有值类型和引用类型以及他们的特征。
 
一、值类型和引用类型的概念
 
        值类型的实例是在线程栈上分配的(不能免俗的提起这句话),值类型的变量并没有一个指向实例的指针,而是变量中已经包含了实例本身的字段。
        相应的引用类型的实例时在托管堆中分配的,返回的是一个指向实例对象的内存地址。
 
        比如我们一个值类型的变量 valType, 他包含一个int的字段a,其值为5,他在栈和堆中的示意图为:
    现有一个引用类型的变量refType,他指向RefType类的一个实例,下图为示意图:
        另外我们都知道基元类型中除了string类型,其他的都是值类型,但是我们大部分人都没有发现他们之间的区别。只要我们进入各种基元类型的定义中就可以发现:string类型是一个class,而其他的值类型都是struct。翻阅资料发现了微软在定义值类型和引用类型的区别:
        引用类型包括类和接口,所有的以class和interface修饰的类型都是引用类型;而值类型包括结构和枚举,所有的结构和枚举都是值类型。继续查找资料发现所有的结构都是抽象类型System.ValueType,所有的枚举都是派生自System.Enum类型的,而System.Enum类型也继承自System.ValueType类。所以我们可以得出值类型都是继承自System.ValueType的结论。
 
        值类型还有一个重要的特征是因为结构是隐式密封的,所以我们没办法由自值类型来派生一个我们想要的类型来。例如我们无法从System.Int32(int)类派生出另外一个类型来。
 
二、为什么要有值类型
        FCL中的绝大多数类型都是引用类型,那么为什么要有值类型呢?首先我们回顾下实例化一个引用类型的步骤:
            1. 计算好在托管堆上分配的内存大小,包括该类型实例的所有字段的大小,以及在托管堆中的两个“额外成员”(《CLR via C#》中的翻译):类型对象指针和同步块索引;
            2. 在托管堆中分配指定大小的内存块;
            3. 初始化对象的“类型对象指针”和“同步块索引”;
            4、调用类型的构造函数,如果调用的是有参构造函数,则还要向其传入参数。注意,如果要实例化子类对象,则必须先调用父类的构造函数,一直会追溯到System.Object。这部分具体的可以参看《C#入门经典》相关章节。
 
        如果所有的类型都是引用类型,那么我们程序的性能将显著下降。引用类型在性能方面还有一个重大的问题是垃圾回收。当没有变量指向某个对象时,那么该对象就会成为垃圾,就会成为GC回收的对象,而GC是相当耗费资源的。而使用值类型是不需要垃圾回收的,对象超过了其作用域就会自动销毁。
 
        上面这段解释了为什么我们需要值类型,讲解了值类型在性能上的又是,那么我们为何还需要引用类型呢?
        
        我们都知道值类型的传值是要复制整个对象的,而引用类型仅仅是复制指向实例对象的指针。而且值类型不能被继承,所以值类型适合一些比较轻量和简单的类型,否则同样会出现性能问题。
        那么我们应该什么时候使用值类型呢:
必须满足以下所有条件,否则不要定义成值类型
第一,类型具有基元类型的行为。类型简单,其中没有成员会修改类型的任何实例字段。
第二,类型不需要从其他任何类型继承。
第三,类型不会派生出其他任何类型。
除了满足以上全部条件,还必须满足以下条件中的一个。
第一,类型的实例较小(约是16字节或者更小)。
第二,类型实例较大,但不作为方法的实参传递,也不通过方法返回。(这样即使很大但是不需实参传递,不会进行复制的操作)
 
        到这里我们基本上把值类型和引用类型的一些基本知识了解完了,那么这部分面试官喜欢问什么问题呢?
            值类型和引用类型的区别
                1. 值类型分配在内存栈上,引用类型分配在托管堆上。当一个值类型的变量赋给另一个值类型的变量时,会执行一次逐字段的复制,而一个引用类型的变量赋给另一个引用类型的变量时,仅仅会复制对象的内存地址。
                2. 基于上一条,多个引用类型的变量可以同时指向同一个对象,对其中的任何一个变量执行操作都会影响到另一个变量引用的对象。而每个值类型的变量都已经包含了自己的对象,所以对值类型对象的操作不会影响到另一个值类型变量。
                3. 值类型包括结构和枚举,他们均间接或直接派生自System.ValueType类;引用类型包括类和接口,他们都派生自System.Object类(这一句是废话,所有的类型都派生自System.Object类,可说可不说)。
                4. 值类型都是隐式密封的,不能将一个值类型作为基类来定义一个新的值类型或者引用类型,也因此值类型中不能包括虚方法(不能被继承,虚方法给谁重写呢)。
                5、默认情况下,创建一个引用类型的变量时,他会被初始化为null;而创建一个值类型时,他的所有成员都会被初始化为0.
                6. 值类型的变量一旦超过了其作用域,为他分配的内存就会被立即释放;而引用类型则会在托管堆里待一段时间,直到垃圾回收器将其回收。
                7. 由于System.ValueType类重写了Equals方法,所以两个值类型的Equals方法会在两个对象的字段完全匹配的情况下返回true;而引用类型的Equals则会在两个变量引用同一个对象的情况下才返回true。(这一条不重要,不说也无所谓,但是如果被问到自己要有所了解)。
 
            数组是值类型还是引用类型?
                我第一次遇到的这个问题的时候并没有特别注意,但是心想他既然问这样的问题,应该是引用类型,所以坚定的回答”引用类型“,让面试官看不出来我是猜的。所以童鞋们以后如果遇到类似的问题,即使是猜的也要理直气壮,否则即使你答对了,面试官也知道你是猜的,在这个问题上还是会被扣分的。
                数组类型的确是引用类型,可能有部分童鞋不承认,那我们简单的写一个验证方法:
            
       #region 数组的类型
            int[] intArray = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            int[] copyArray = intArray;
            intArray[0] = 9;
            Console.WriteLine(copyArray[0]);
            #endregion

                输出为:9.

 
            结构和类的区别
                实际上就是值类型和引用类型的区别,对照着第一题回答就行了。
 
        到这里,值类型和引用类型还有一个特别重要的点没有提到,那就是装箱和拆箱。实际上是一个很简答的问题,但是如果你不了解,在面试时会很难回答。并且在日常的工作中很有可能经常掉入装箱的陷阱,对程序的性能有较大的影响。所以我打算下一篇就将装箱和拆箱。
 
        好了,第一篇写完了,希望大家给给建议,我希望和大家共同努力成长。
        
posted @ 2013-03-17 15:27  zhangkai2237  阅读(4310)  评论(9编辑  收藏  举报