zoukankan      html  css  js  c++  java
  • 【.Net】浅谈C#中的值类型和引用类型

    在C#中,值类型和引用类型是相当重要的两个概念,必须在设计类型的时候就决定类型实例的行为。如果在编写代码时不能理解引用类型和值类型的区别,那么将会给代码带来不必要的异常。很多人就是因为没有弄清楚这两个概念从而在编程过程中遇到了很多问题,在这里博主浅谈对值类型和引用类型的认识。

    首先从概念上看,值类型直接存储其值,而引用类型存储对其值的引用。从而这两种类型存储在内存的不同地方。

    其次从内存空间上看,值类型是在栈中操作,而引用类型则在堆中分配存储单元。

    栈在编译的时候就分配好内存空间,在代码中有栈的明确定义,而堆是程序运行中动态分配的内存空间,可以根据程序的运行情况动态地分配内存的大小。因此,值类型总是在内存中占用一个预定义的字节数。而引用类型的变量则在栈中分配一个内存空间,这个内存空间包含的是对另一个内存位置的引用,这个位置是托管堆中的一个地址,即存放此变量实际值的地方。

    也就是说值类型相当于现金,要用就直接用,而引类型相当于存折,要用得先去银行取。

    但值类型在栈上分配内存,而引用类型在托管堆上分配内存,只是一种笼统的说法。下面对其进行详细描述。

    (1)对于值类型的实例,如果作为方法中的局部变量,则被创建在线程栈上;如果该实例作为类型的成员,则作为类型成员的一部分,连同其他类型字段存放在托管堆上。

    每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。例如:

    int i = new int(); 

    等价于:

    Int32 i = new Int32(); 

    等价于:

    int i = 0; 

    等价于:

    Int32 i = 0; 

    使用new运算符时,将调用特定类型的默认构造函数并对变量赋以默认值。在上例中,默认构造函数将值0赋给了i。

    说明:C#的所有值类型均隐式派生自System.ValueType,而System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

    (2)引用类型的实例创建在托管堆上。

    下面以一段代码来详细讲解一下值类型与引用类型的区别

     1    namespace Test 
     2    {  
     3            class Program  
     4            {  
     5                static void Main(string[] args)  
     6                {  
     7               //调用ReferenceAndValue类中的Demonstration方法  
     8                    ReferenceAndValue.Demonstration();  
     9                    Console.ReadLine();  
    10                }  
    11            }  
    12            public class stamp             //定义一个类  
    13            {  
    14                public string Name { get; set; }       //定义引用类型  
    15                public int Age { get; set; }       //定义值类型  
    16            }  
    17            public static class ReferenceAndValue      //定义一个静态类  
    18            {  
    19                public static void Demonstration()     //定义一个静态方法  
    20                {  
    21                    stamp Stamp_1 = new stamp { Name = "Premiere", Age = 25 }; //实例化  
    22                    stamp Stamp_2 = new stamp { Name = "Again", Age = 47 }; //实例化  
    23                    int age = Stamp_1.Age;         //获取值类型Age的值  
    24                    Stamp_1.Age = 22;          //修改值类型的值  
    25                    stamp guru = Stamp_2;          //获取Stamp_2中的值  
    26                    Stamp_2.Name = "Again Amend";      //修改引用的Name值  
    27                    Console.WriteLine("Stamp_1's age:{0}", Stamp_1.Age); //显示Stamp_1中的Age值  
    28                    Console.WriteLine("age's value:{0}", age); //显示age值  
    29                    Console.WriteLine("Stamp_2's name:{0}", Stamp_2.Name); //显示Stamp_2中的Name值  
    30                    Console.WriteLine("guru's name:{0}", guru.Name); //显示guru中的Name值  
    31                }  
    32        }  
    33    } 

    通过运行上面一段程序之后我们可以看出,当改变了Stamp_1.Age的值时,age并没有跟着变,但在改变了anders.Name的值后,guru.Name却跟着变了,这就是值类型和引用类型的区别。在声明age值类型变量时,将 Stamp_1.Age的值赋给它,这时,编译器在栈上分配了一块空间,然后把Stamp_1.Age的值填进去,二者没有任何关联,就像在计算机中复制文件一样,只是把Stamp_1.Age的值拷贝给age了。而引用类型则不同,在声明guru时把Stamp_2赋给它,前面说过,引用类型包含的只是堆上数据区域地址的引用,其实就是把Stamp_2的引用也赋给guru,因此它们指向了同一块内存区域。既然是指向同一块区域,不管修改谁,另一个的值都会跟着改变,就像信用卡跟亲情卡一样,用亲情卡取了钱,与之关联的信用卡账上也会跟着发生变化。

  • 相关阅读:
    初试 spring web mvc
    读取网络数据缓存在本地 流程图
    servlet 过滤器实现 请求转发(跳转);跨域转发请求;tomcat 环境下。
    C# .net基于Http实现web server(web服务)
    微信公众平台开发
    Linux目录结构及作用
    MySQL事件调度器event的使用
    MySQL触发器trigger的使用
    存储过程的查、改、删
    MySQL游标的简单实践
  • 原文地址:https://www.cnblogs.com/yanglang/p/7613752.html
Copyright © 2011-2022 走看看