值类型与引用类型

声明:部分类容转载自Hawkitc,地址:http://blog.csdn.net/qiaoquan3/article/details/51202926

1、值类型变量与引用类型变量

  从概念上看,值类型直接存储其值,而引用类型存储对其值的引用。  

  声明一个值类型变量,编译器在内存的栈上分配一个空间,这个空间对应着该值类型变量,空间里存储的就是该变量的值。(注意:声明后不管是否已经赋值,编译器为其分配内存。)

  声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。

  栈的内存是自动释放的,堆内存是.NET中会由GC来自动释放

2、值类型与引用类型都有哪些?

值类型:C#的所有值类型均隐式派生自System.ValueType:

  • 结构体:struct(直接派生于System.ValueType);
  • 数值类型:
  1. 整型:sbyte(System.SByte的别名),short(System.Int16),int(System.Int32),long(System.Int64),byte(System.Byte),ushort(System.UInt16),uint(System.UInt32),ulong(System.UInt64),char(System.Char);
  2. 浮点型:float(System.Single),double(System.Double);
  3. 用于财务计算的高精度decimal型:decimal(System.Decimal)。
  • bool型:bool(System.Boolean的别名);
  • 用户定义的结构体(派生于System.ValueType)。
  • 枚举:enum(派生于System.Enum);
  • 可空类型(派生于System.Nullable<T>泛型结构体,T?实际上是System.Nullable<T>的别名)。

C#有以下一些引用类型:

  • 数组(派生于System.Array)
  • 用户用定义的以下类型:
  1. 类:class(派生于System.Object);
  2. 接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。Anders在《C# Programming Language》中说,接口只是表示一种约定[contract]);
  3. 委托:delegate(派生于System.Delegate);
  • object(System.Object的别名);
  • 字符串:string(System.String的别名)。

3、值类型复制与引用类型复制

  值类型在复制的时候,传递的是这个值得本身。(即在栈上开辟一个内存,把值复制过去)
  引用类型在复制的时候,传递的是对这个对象的引用。(即在在栈上开辟一个内存,存放实际数据在堆中的地址,这个地址指向堆中的同一个对象)

 int n1 = 10;
 int n2 = n1;
      n2 = 20;
Console.WriteLine("n1的值是{0},n2的值是{1}。",n1,n2);

运行结果:n1的值是10,n2的值是20

值类型传递在栈中的地址是不同的。

 Person p1 = new Person();
            p1.Name = "张三";
            Person p2 = p1;
            p2.Name = "李四";
            Console.WriteLine(p1.Name);

运行结果:李四

通过即时窗口不难看出为何改变p2的变量的值,会引起p1的变量值的变化。p1、p2指向的堆中的同一个地址 0x02391268

  注意:字符串string虽然也是引用类型,但是由于其的不可变性,每次赋值都是在堆上开辟新的空间,不会覆盖原来

***值得注意“对象赋值”

为对象进行“赋值”的时候,情况却发生了变化,对一个对象进行操作时,实际上操作的是它的一个“引用”,或者称为“句柄”,所以假如“从一个对象到另一个对象”赋值,实际上就是将“引用”从一个地方复制到另一个地方。这意味着假若为对象使用“C=D”,那么C和D最终都会指向最初只有D才指向的那个对象。下面的例子将更加清晰的阐述这一点。
下面的例子:
  

class Number
        {
            private int temp;
            public int Temp
            {
                get { return temp; }
                set { temp = value; }
            }
        }
        static void Main(string[] args)
        {
            Number n1 = new Number();
            Number n2 = new Number();
            n1.Temp = 9;
            n2.Temp = 47;
 
            Console.WriteLine("1:n1.Temp:" + n1.Temp + ",n2.Temp:" + n2.Temp);
            n1 = n2;
            Console.WriteLine("1:n1.Temp:" + n1.Temp + ",n2.Temp:" + n2.Temp);
            n1.Temp = 27;
            Console.WriteLine("1:n1.Temp:" + n1.Temp + ",n2.Temp:" + n2.Temp);
            Console.ReadLine();
        }

Number 类非常简单,它的两个实例(n1 和n2)是在main()里创建的。每个Number 中的i 值都赋予了一个不同的值。随后,将n2 赋给n1,而且n1 发生改变。在许多程序设计语言中,我们都希望n1 和n2 任何时候都相互独立。但由于我们已赋予了一个“引用”,所以下面才是真实的输出:
1: n1.Temp: 9, n2.Temp: 47
2: n1.Temp: 47, n2.Temp: 47
3: n1.Temp: 27, n2.Temp: 27
看来改变n1 的同时也改变了n2!这是由于无论n1 还是n2 都包含了相同的“引用”,它指向相同的对象(最初的“引用”位于n1 内部,指向容纳了值9 的一个对象。在赋值过程中,那个“引用”实际已经丢失;它的对象会由“垃圾收集器”自动清除)。这种特殊的现象通常也叫作“别名”,是.net操作对象的一种基本方式。但假若不愿意在这种情况下出现别名,又该怎么操作呢?可放弃赋值,并写入下述代码:
n1.Temp = n2.Temp;
这样便可保留两个独立的对象,而不是将n1 和n2 绑定到相同的对象。但您很快就会意识到,这样做会使对象内部的字段处理发生混乱,并与标准的面向对象设计准则相悖。

原文地址:https://www.cnblogs.com/apollo-shen/p/6804763.html