代码改变世界

C# 值类型和引用类型

2011-08-16 23:31  DylanChan  阅读(272)  评论(0编辑  收藏  举报
我们都知道,c#的两大数据类型分别为值类型和引用类型。很多人或许闭着眼睛都能说出值类型包括简单类型、结构体类型和枚举类型,引用类型包括自定义类、数组、接口、委托等,但是当被问及到二者之间的联系和区别,什么时候用struct什么时候用class时,就常常混淆不清了。为此,了解值类型和引用类型的本质差异就变的很有必要了。    

◆值类型直接存储其值,变量本身就包含了其实例数据,而引用类型保存的只是实例数据的内存引用。因此,一个值类型变量就永远不会影响到其他的值类型变量,而两个引用类型变量则很有可能指向同一地址,从而发生相互影响。

从内存分配上来看,值类型通常分配在线程的堆栈(Sack)上,作用域结束时,所占空间自行释放,效率高,无需进行地址转换,而引用类型通常分配在托管堆(Heap)上,由GC来控制其回收,需要进行地址转换,效率降低,这也正是c#需要定义两种数据类型的原因之一。

◆值类型均隐式派生自System.ValueType,而System.ValueType又直接派生于System.Object,每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值,注意所有的值类型都是密封(sealed)的,所以无法派生出新的值类型。而且System.ValueType本身是一个类类型,而不是值类型,因为它重写了object的Equals()方法,所以对值类型将按照实例的值来比较,而不是比较引用地址。

◆C# 的统一类型系统,使得值类型可以转化为对象来处理,这就是常说的装箱和拆箱。由于装拆箱需要装建全新对象或做强制类型转换,这些操作所需时间和运算要远远大于赋值操作,因此不提倡使用它,同时也要尽量避免隐式装拆箱的发生。

栈是操作系统分配的一个连续的内存区域,用于快速访问数据。因为值类型的容量是已知的,因此它可存储在栈上。

托管堆是CLR在应用程序启动时为应用程序预留的一块连续内存区,是用于动态内存分配的内存区,引用类型的容量只有到运行时才能确定,所有用堆来存储引用类型。

string类型

string是一个很有意思的引用类型,为什么说它很有意思呢?因为它表现了很多值类型的特点
    class Program
    {
        static void Main()
        {
            string str1 = "abc";
            string str2 = str1;
            str1 = "123";
            Console.WriteLine(str2);    //输出结果是abc,改变str1的值对str2没有影响。

            string a = "hello";
            string b = "h";
            // Append to contents of 'b'
            b += "ello";
            Console.WriteLine(a == b);      //输出结果是True
        }
    }
  这样的结果会使我们误以为string就是值类型。其实不然,示例1中str1 = "123"语句编译器私底下创建了一个新的字符串对象来保存新的字符序列"123",也就是此str1已非彼str1了,“此”str1的值的改变也就不能影响“彼”str1的值了,当然str2的值也就不会改变了。实质上str1 = "123"是str1=new string("123")的简写,它的每一次赋值都会抛掉原来的对象而生成一个新的字符串对象,分配新的内存空间,因此string是不可改变的。如果要创建可修改的字符串,可使用stringbuilder以获得更好的性能。至于示例2是因为为了方便比较字符串的值重定义了string的运算符== 和 !=。
 
struct和class

   class和struct的语法基本相同,从声明到使用,都很相似。但是struct的约束要比class多,理论上,struct能做到的class都能做到,但class能做到的stuct却不一定做的到,也就是说struct都能被class所代替。那么为什么还要使用struct呢?存在即是合理的,struct在很多方面有着性能优势。让我们看看它们的主要区别在哪里?

◆从继承性来看,struct既不能继承也不能被继承,但是可以实现接口,而Class就可以完全扩展了
◆内部结构有区别,struct只能添加带参的构造函数,不能使用abstract和protected 等修饰符,不能初始化实例字段,但是值得注意的是,struct可以重写System.Object的3个虚方法,Equals()、 ToString()和GetHashTable(),Class没有这些限制
◆当你实例一个class,它将创建在堆上。而你实例一个struct,它将创建在栈上
◆你使用的是一个对class实例的引用。而你使用的不是对一个struct的引用。(而是直接使用它 们)
◆当我们将class作为参数传给一个方法,我们传递的是一个引用。struct传递的是值而非引 用。
◆数据类型不一样,struct是值类型,class是引用类型,因此它们具有所有值类型和引用类型之间的差异。由于堆栈的执行效率要比堆的执行效率高,但是堆栈资源却很有限,不适合处理逻辑复杂的大对象,因此struct常用来处理作为基类型对待的小对象,而class来处理某个商业逻辑。既然class是引用类型,class可以设为null。但是我们不能将struct设为null,因为它是值类型。
    struct AStruct
    {
        int aField;
    }
    class AClass
    {
        int aField;
    }
    class MainClass
    {
        public static void Main()
        {
            AClass b = null; // No error.
            AStruct s = null; // Error [ Cannot convert null to 'AStruct' because it is a value type ].
        }
    }
◆structs 不可以有初始化器,class可以有初始化器。
    class MyClass
    {
        int myVar = 10; // no syntax error. 
    }
    struct MyStruct
    {
        int myVar = 10; // 结构中不能有实例字段初始值设定项
    }
◆Class 可以有明显的无参数构造器,但是Struct不可以
    class MyClass
    {
        int myVar = 10;
        public MyClass() // no syntax error. 
        {
            // statements
        }
    }
    struct MyStruct
    {
        int myVar;
        public MyStruct() //结构不能包含显式的无参数构造函数
        {
            // statements 
        }
    }
◆Class使用前必须new关键字实例化,Struct不需要
    class Program
    {
        static void Main()
        {
            MyClass mc = new MyClass();       //类使用前必须new关键字实例化
            Console.WriteLine(mc.myVar);
            MyStruct ms;
            ms.myVar = 100;
        }
    }
    class MyClass
    {
        public int myVar = 10;
    }
    struct MyStruct
    {
        public int myVar;
    }
◆Class的构造函数不需要初始化全部字段,Struct的构造函数必须初始化所有字段
    class MyClass
    {
        int myInt;
        string myString;
        public MyClass(int aInt)
        { 
            //Statement
        }
    }
    struct MyStruct 
    {
        public int myInt;
        public string myString;
        public MyStruct(int aInt)
        {
            myInt = 3;
            myString = "abc";
        }
    }