值类型和引用类型区别

来源:互联网 发布:c语言后缀表达式求值 编辑:程序博客网 时间:2024/06/05 20:49

3.1.3 值类型和引用类型的区别 37

· 值类型和引用类型的基本概念
· 
值类型和引用类型的内存分配
· 
值类型的基类
  
分析问题
所有.NET的类型都可以分为两类:值类型和引用类型。最简单也最明确的一个区分标准是:所有的值类型都继承自System.ValueTypeSystem.ValueType继承自System.Object),也就是说,所有继承自System.ValueType的类型都是值类型,而其他类型都是引用类型。常用的值类型包括结构、枚举、整数型、浮点型、布尔型等,而在C#中所有以class关键字定义的类型都是引用类型。
严格来说,System.Object作为所有内建类型的基类,本身并没有值类型和引用类型之分。但是System.Object的对象,具有引用类型的特点。这也是值类型在有些场合需要装箱拆箱操作的原因。
1
.赋值时的区别
引用类型和值类型最显著的一个区别在于变量的赋值问题。值类型的变量将直接获得一个真实的数据副本,而对引用类型的赋值仅仅是把对象的引用赋给变量,这样就可能导致多个变量引用到一个实际对象实例上。
来看一段简单的示例:代码3-2。首先为了测试建立一个简单的引用类型和一个简单的值类型。
代码3-2  值类型引用类型赋值:ValueRef.cs
    /// <summary>
    /// 
一个简单的引用类型
    /// </summary>
    public class Ref
    {
        private int _int;
        public Ref(int i)
        {
            _int = i;
        }
        public int I
        {
            get
            {
                return _int;
            }
            set
            {
                _int = value;
            }
        }
        public override string ToString()
        {
            return "I 
的值为:"+ _int.ToString();
        }
    }
    /// <summary>
    /// 
一个简单的值类型
    /// </summary>
    public struct Val
    {
        private int _int;
        public Val(int i)
        {
            _int = i;
        }
        public int I
        {
            get
            {
                return _int;
            }
            set
            {
                _int = value;
            }
        }
        public override string ToString()
        {
            return "I 
的值为:"+ _int.ToString();
        }
    }
随后编写main方法,测试对值类型和引用类型对象进行赋值的不同结果,如代码3-3所示。
代码3-3  值类型引用类型赋值:ValueRef.cs
    public class ValueRef
    {
        static void Main(string[] args)
        {
            //
测试引用类型的赋值
            Ref ref1 = new Ref(1);
            Ref ref2 = ref1;
            ref2.I = 2;
            //
测死值类型的赋值
            Val val1 = new Val(1);
            Val val2 = val1;
            val2.I = (2);
            //
输出
           Console.WriteLine("ref1 " + ref1);
           Console.WriteLine("ref2 " + ref2);
           Console.WriteLine("val1 " + val1);
           Console.WriteLine("val2 " + val2);
            Console.Read();
        }
    }
简单分析一下代码3-2和代码3-3,程序定义了一个引用类型Ref和一个值类型Val,两者的内容几乎完全相同。在main方法中,分别测试了引用类型和值类型的赋值。当代码把一个引用类型变量赋值给另一个引用变量:Ref ref2=ref1时,实际上把ref1的对象引用赋给了ref2,这样,两个引用变量实际指向了同一个对象。而值类型的赋值则不同,val1val2 都保留了属于自己的数据副本,所以当val2改变时,val1不受到影响。以下是代码3-2和代码3-3的输出结果:
ref1 I 
的值为:2
ref2 I 
的值为:2
val1 I 
的值为:1
val2 I 
的值为:2
2
.内存分配的区别
除了赋值的区别,引用类型和值类型在内存的分配位置上也有区别。引用类型的对象将会在堆上分配内存,而值类型的对象则会在堆栈上分配内存。堆栈的空间相对有限,但运行效率却比堆高得多,关于堆和堆栈的特点,将在本章的后续章节中有详细的说明。
3
.来自继承结构的区别
最后,由于所有的值类型都有一个共同的基类:System.ValueType,所以值类型拥有一些引用类型不具有的共同性质,较重要的一点是值类型的比较方法:Equals方法的实现有了改变。在前文中笔者已经谈到,所有的值类型已经实现了内容的比较,而引用类型在没有重写Equals方法的情况下,仍然采用引用比较。代码3-4展示了这个特性,代码3-4仍然使用了代码3-2中建立的值类型和引用类型,这里不再重复列出这两个类型的定义。
代码3-4  值类型引用类型比较:ValueRefEquals.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace NET.MST.Third.ValueRefEquals
{
    public class ValueRefEquals
    {
        static void Main(string[] args)
        {
            //
引用类型
            Ref ref1 = new Ref(1);
            Ref ref2 = new Ref(1);
            //
值类型  struct类型
            Val val1 = new Val(1);
            Val val2 = new Val(1);
            //
输出
           Console.WriteLine(ref1.Equals(ref2));
           Console.WriteLine(val1.Equals(val2));
            Console.Read();
        }
    }
}
main方法中,分别申明了一对内容完全相同的值类型对象和引用类型对象,调用Equals方法来比较,发现值类型对象比较返回true,而引用类型对象比较返回false。以下是代码3-4的执行结果:
False
True
  
答案
所有继承自System.ValueType的类型都是值类型,而其他类型都是引用类型。值类型的赋值会产生一个新的数据副本,所以每个值类型都拥有一个数据副本,而引用类型的赋值则是赋值引用。值类型的对象分配在堆栈上,而引用类型的对象分配在堆上。当比较两个值类型时,进行的是内容比较,而比较两个引用类型时,进行的是引用比较。
笔者这里列举的,仅仅是值类型和引用类型的一些主要区别,通过这些本质区别,可以产生更多的细节区别,有兴趣的读者可以自己总结整理。

0 0