1、 基础知识
1) 值类型与引用类型
下面类型哪些是值类型?哪些是引用类型?
i. class enum struct delegate interface
ii. object int string float DateTime
【答案】
值类型有:enum struct int float DateTime
引用类型有:class delegate interface object string
特别要注意的是:string是引用类型
值类型与引用类型的区别:
1、内存分配方式不同:
值类型所占的内存区域是连续的,至于是分配在堆上还是栈上,有以下几种情况:
1) 如果值类型Struct1用来定义局部变量,那么它总是分配在栈上;
2) 如果值类型Struct1用来定义引用类型Class1的字段,那么Struct1总是分配在堆上的;
3) 如果值类型Struct1用来定义值类型Struct2的字段,那么Struct1的分配方式取决于Struct2是分配在堆上还是栈上;
4) 如果把值类型Struct1赋予一个引用类型Class1(这种情况通常见于把一个值类型赋予object变量,或赋予一个接口),这时会在堆上建立一个Struct1的拷贝,此过程称之为“装箱”;
引用类型的内存区域由两部分组成:一部分为引用(类似于C++中的指针),另一部分为实际的对象,对象本身总是分配在堆上的,而引用分配在堆上还是栈上取决于具体的情况:
1) 如果定义局部变量,那么引用是分配在栈上的;
2) 如果用于定义引用类型的字段,那么引用是分配在堆上的;
3) 如果用于定义值类型的字段,那么引用的分配方式取决于该值类型分配在堆上还是栈上;
在堆上和栈上的内存分配有如下的区别:
1) 在栈上定义的对象(或引用),可以在出栈的时候迅速释放,而不必等待无用内存回收器来回收;栈上定义的对象无法持久存储;
2) 栈的空间有限,而堆的空间比较大;
2、变量传递方式不同
引用类型在传递的时候,只传递引用本身,而对象只有一个;
值类型在传递的时候会进行对象的拷贝;
3、对象释放的方式不同
在对象是否有效的判断上,引用类型是进行引用计数,当引用计数为0时,那么它就会被标为无效,而等待无用内存回收器来收集;
值类型如果分配在堆上,则会分情况而定,如果是作为引用类型的字段而被分配在堆上,则与该对象同存共亡,如果以装箱的方式而分配在堆上,则也与引用类型一样进行引用计数;
如果值类型是分配在栈上,则它的生存期与进栈出栈有关,一对花括号表示一个域,花括号中嵌套的花括号是它的子域,当值类型在进入直接包含它的域的时候被分配,而出域时被释放。
值类型和引用类型都有释构函数,在对象的生存期终止时,值类型会立即执行析构函数,而引用类型是等无用内存收集器运行时执行;无用内存回收器的运行会引起应用程序的暂停,如果析构函数比较耗时,那么将会引起应用程序明显的停顿,这是我们不推荐写析构函数的原因。(但有的情况下析构函数还是比较有价值的)
4、对象是否相等的比较方式不同
如果没有重载==运行符,那么引用类型的相等的判断,是两个引用是否引用了同一个对象;值类型如果要进行是否相等的判断,必须重载==运算符;
5、默认值及构造方式不同
引用类型可以不引用任何对象,此时它的值为null,null是引用类型的默认值;值类型的默认值比较复杂,对于基元类型(int、long、float、double等),默认值为0,bool的默认值为false,复合的值类型,是把各个字段都取其默认值;
在对象的构造上,引用类型默认有一个无参构造函数,但如果定义了其它的构造函数,那么该无参构造函数被自动删除;值类型也有一个无参构造函数,但如果定义了其它构造函数,该无参构造函数依然存在,我们也不能明确定义一个无参构造函数,并且在定义的其它构造函数中,必须对所有字段进行赋值,否则会出现编译错误;
6、类层次结构不同
引用类型可以自由地派生,但值类型具有固定的类层次结构,其中struct类型是派生于System.ValueType,enum类型派生于System.Enum,而System.Enum派生于System.ValueType;并且所有的值类型都是密封的,我们不能把值类型用作其它类型的基类;
7、如何在值类型与引用类型中选择
如果我们定义一个类型,通常情况下应该定义为引用类型,引用类型在参数传递的效率上具有优势;
值类型由于内存结构比较简单,它在创建和分配的效率上具有优势(不需要引用计数);但由于值类型在传递的时候会引起拷贝,所以值类型的尺寸不适合定义得过大;
定义值类型和引用类型的初衷也是不同的,值类型用于数据的简单存储,例如int、long、float、double等,它们不会有多少与架构相关的东西;而引用类型的类层次结构的灵活性,决定了它是定义软件架构的基石;
.NET已经为我们提供了一些值类型,例如int、long、float、double等,这些称为基元类型,通常情况下,我们是把这些基元类型进行组合,形成自己的更有意义的值类型,例如定义坐标(x,y),定义一个值类型Point比用两个int来表示更有意义,并且还可以在Point中定义一些有意义的方法,或重载一些运算符等;
2) 非静态成员与静态成员
写出下面代码的输出结果:
class MyClass
{
public MyClass() { v1++; v2++; }
public static int v1;
public int v2;
}
// 注意:为了书写方便,没有将下面的代码放在函数中,下同
MyClass mc1 = new MyClass(), mc2 = new MyClass();
Console.WriteLine(“{0} {1} {2}”, MyClass.v1, mc1.v2, mc2.v2);
【答案】
2 1 1