.Net Framemwork 之 值类型和引用类型的存储

  C#把数据类型分为两种:值类型 和 引用类型。值类型存储在堆栈中,而引用类型存储在托管堆上。

一、值类型和引用类型变量的存储
  首先,变量是存储信息的基本单元,而对于计算机内部来说,变量就相当于一块内存空间。
  C#中的变量数据类型有两种:
  [1] 值类型:简单类型、结构类型、枚举类型
  [2] 引用类型:类、代表、数组、接口。

1、值类型和引用类型内存分配
   值类型是在栈中操作,而引用类型则在堆中分配存储单元。

  栈在编译的时候就分配好内存空间,在代码中有栈的明确定义,而堆是程序运行中动态分配的内存空间,可以根据程序的运行情况动态地分配内存的大小。因此,值类型总是在内存中占用一个预定义的字节数(比如,int占用4个字节,即32位)。当声明一个值类型变量时,会在栈中自动分配此变量类型占用的内存空间,并存储这个变量所包含的值。.NET会自动维护一个栈指针,它包含栈中下一个可用内存空间的地址。栈是先入后出的,栈中最上面的变量总是比下面的变量先离开作用域。当一个变量离开作用域时,栈指针向下移动被释放变量所占用的字节数,仍指向下一个可用地址。

  注意,值类型的变量在使用时必须初始化。
  引用类型的变量则在栈中分配一个内存空间,这个内存空间包含的是对另一个内存位置的引用,这个位置是托管堆中的一个地址,即存放此变量实际值的地方。.NET也自动维护一个堆指针,它包含堆中下一个可用内存空间的地址,但堆不是先入后出的,堆中的对象不会在程序的一个预定义点离开作用域,为了在不使用堆中分存的内存时将它释放,.NET将定期执行垃圾收集。垃圾收集器递归地检查应用程序中所有的对象引用,当发现引用不再有效的对象使用的内存无法从程序中访问时,该内存就可以回收(除了fixed关键字固定在内存中的对象外)。
  但值类型在栈上分配内存,而引用类型在托管堆上分配内存,却只是一种笼统的说法。更详细准确地描述是:
  [1] 对于值类型的实例,如果做为方法中的局部变量,则被创建在线程栈上;如果该实例做为类型的成员,则作为类型成员的一部分,连同其他类型字段存放在托管堆上,
  [2] 引用类型的实例创建在托管堆上,如果其字节小于85000byte,则直接创建在托管堆上,否则创建在LOH(Large Objet Heap)上。

  比如一下代码段:

    public class Test
     {
         private int i;    //作为Test实例的一部分,与Test的实例一起被创建在GC堆上
         public Test()
         {
             int j = 0;     //作为局部实量,j的实例被创建在执行这段代码的线程栈上
         }
     }

2、嵌套结构的内存分配
    所谓嵌套结构,就是引用类型中嵌套有值类型,或值类型中嵌套有引用类型。
    引用类型嵌套值类型是最常见的,上面的例子就是典型的例子,此时值类型是内联在引用类型中。
    值类型嵌套引用类型时,该引用类型作为值类型成员的变量,将在堆栈上保留关引用类型的引用,但引用类型还是要在堆中分配内存的。

3、关于数组内存的分配
    考虑当数组成员是值类型和引用类型时的情形:
    成员是值类型:比如int[] arr = new int[5]。arr将保存一个指向托管堆中4*5byte(int占用4字节)的地址的引用,同时将所有元素赋值为0;
    引用类型:myClass[] arr = new myClass[5]。arr在线程的堆栈中创建一个指向托管堆的引用。所有元素被置为null。
   
二、值类型和引用类型在传递参数时的影响
    由于值类型直接将它们的数据存放在栈中,当一个值类型的参数传递给一个方法时,该值的一个新的拷贝被创建并被传递,对参数所做的任何修改都不会导致传递给方法的变量被修改。而引用类型它只是包含引用,不包含实际的值,所以当方法体内参数所做的任何修改都将影响传递给方法调用的引用类型的变量。
    下面程序证明了这一点:

   class Program
     {
         /// <summary>
         /// 应用程序的主入口点。
         /// </summary>
         [STAThread]
         static void Main(string[] args)
         {
             int i = 0;
             int[] intArr = new int[5];

            Class1.SetValues(i,intArr);
            //输出的结果将是:i=0,intArr[0]=10
            Console.WriteLine("i={0},intArr[0]={1}",i,intArr[0]);
            Console.Read();

        }

        public static void SetValues(int i,int[] intArr)
         {
             i = 10;
             for (int j = 0; j < intArr.Length; j++)
             {
                 intArr[j] = i;
             }
         }
     }

三、装箱和拆箱
   装箱:将一个值类型转换为一个对象类型(object);

  拆箱:将一个对象类型显式转换为一个值类型。

  对于装箱而言,它是将被装箱的值类型复制一个副本来转换,而对于拆箱而言,需要注意类型的兼容性,比如,不能将一个值为“a”的object类型转换为int的类型。
  可以用以下程序来说明:

        static void Main(string[] args)
         {
             int i = 10;

            //装箱
             object o = i; //对象类型

            if (o is int)
             {
                 //说明已经被装箱
                 Console.WriteLine("i已经被装箱");
             }

            i = 20; //改变i的值

            //拆箱
             int j = (int)o; 

            //输出的结果是20,10,10
             Console.WriteLine("i={0};o={1};j={2}",i,o,j);
             Console.ReadLine();
         }

四、关于string
    string是引用类型,但却与其他引用类型有着一点差别。可以从以下两个方面来看:
    (1)String类继承自object类。而不是System.ValueType。
    (2)string本质上是一个char[],而Array是引用类型,同样是在托管的堆中分配内存。
    但String作为参数传递时,却有值类型的特点,当传一个string类型的变量进入方法内部进行处理后,当离开方法后此变量的值并不会改变。原因是每次修改string的值时,都会创建一个新的对象。比如下面这段程序:
         
class Class1
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            string a = "1111";        //a是一个引用,指向string类的一个实例
            string b = a;             //b与a都是同一个对象
           
            //这时候b与a指向的并不是同一样对象,因为给b赋值后,已经创建了一个新的对象,并将这个新的string对象的引用赋给了b。
            b = "2222";

//所以a的值不变,输出a=111.
            Console.WriteLine("a={0}",a);
            Console.ReadLine();
        }
    }
    但要注意,如果按引用传值时,则会与引用类型的参数一样,值会发生改变,比如以下代码:
         
class Class1
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            string a = "1111";       
            TestByValue(a);

//输出a=111.
            Console.WriteLine("a={0}",a);

TestByReference(ref a);

//按引用传值时则会改变,输出a="".
            Console.WriteLine("a={0}",a);
            Console.ReadLine();
        }

static void TestByValue(string s)
        {
            //设置值
            s = "";
        }

static void TestByReference(ref string s)
        {
            //设置值
            s = "";
        }
    }
五、关于C#中的堆和栈
    C#中存储数据的地方有两种:堆和栈。
    在传统的C/C++语言中,栈是机器操作系统提供的数据结构,而堆则是C/C++函数提供的。所以机器有专门的寄存器来指向栈所在的地址,有专门的机器指令实现数据的入栈/出栈动作。其执行效率高,但不过也正因为此,栈一般只支持整数、指针、浮点数等系统直接支持的类型。堆是由C/C++语言提供函数库来维护的,其内存是动态分配的。相对于堆来说,栈的分配速度快,不会有内存碎片,但支持的数据有限。
    在C#中,值变量由系统分配在栈上。用来分配固定长度的数据(值类型大都有固定长度)。每一个程序都有单独的堆栈,其他程序不能访问。在调用函数时,调用函数的本地变量都被推入程序的栈中。与C/C++类似,堆用来存放可变长度的数据,不过与C/C++不同的是,C#中数据是存放在托管堆中。
    由于值变量在栈中分配,所以把一个值变量赋给另一个值变量,会在栈中复制两个相同数据的副本;相反,把一个引用变量赋给另一个引用变量时,会在内存中创建对同一个位置的引用。
    在栈中分配相对于堆中分配,有以下特点:
    (1)分配速度快;
    (2)用完以后自动解除分配;
    (3)可以用等号的方式把一个值类型的变量赋给另一个值类型。

时间: 2025-01-17 23:08:48

.Net Framemwork 之 值类型和引用类型的存储的相关文章

定义类+类实例化+属性+构造函数+匿名类型var+堆与栈+GC回收机制+值类型与引用类型

为了让编程更加清晰,把程序中的功能进行模块化划分,每个模块提供特定的功能,而且每个模块都是孤立的,这种模块化编程提供了非常大的多样性,大大增加了重用代码的机会. 面向对象编程也叫做OOP编程 简单来说面向对象编程就是结构化编程,对程序中的变量结构划分,让编程更清晰. 类的概念: 类实际上是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法. 类定义了类的每个对象(称为实例)可以包含什么数据和功能. 类中的数据和函数称为类的成员:数据成员        函数成员 数据成员: 数据成员

Java值类型和引用类型

[定义] 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值, 那么调用这个方法是传入的变量的值也将改变.值类型表示复制一个当前变量传给方法, 当你在这个方法中改变这个变量的值时,最初生命的变量的值不会变.通俗说法: 值类型就是现金,要用直接用:引用类型是存折,要用还得先去银行取现.----(摘自网上) [值类型] 也就是基本数据类型 基本数据类型常被称为四类八种 四类: 1,整型 2,浮点型 3,字符型4,逻辑型 八种: 1,整型3种 by

java 值类型和引用类型的区别

1. Java中值类型和引用类型的不同? [定义] 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值, 那么调用这个方法是传入的变量的值也将改变.值类型表示复制一个当前变量传给方法, 当你在这个方法中改变这个变量的值时,最初生命的变量的值不会变.通俗说法: 值类型就是现金,要用直接用:引用类型是存折,要用还得先去银行取现.----(摘自网上) [值类型] 也就是基本数据类型 基本数据类型常被称为四类八种 四类: 1,整型 2,浮点型 3,字

c# 值类型与引用类型 值传递与引用传递

值类型与引用类型: 值类型 :1.值类型大小固定.存储在栈上.  2.不能继承,只能实现接口 3.派生自valuetype int double char float byte bool enum struct decimal 引用类型:1.在栈上存储了一个地址实际存储在堆中,大小不固定. 2.数组.类.接口.委托 string 数组 类 接口 委托 值传递与引用传递: 值类型按值传递.值类型按引用传递.引用类型按值传递.引用类型按引用传递. 值传递:默认传递都是值传递 ,把栈中内容拷贝一份引用

值类型和引用类型的区别

数据项的类型定义了存储数据需要的内存大小,组成该类型的数据成员以及该类型能执行的函数.类型还决了对象在内存中的存储位置-栈或堆. 类型被分为:值类型和引用类型,这两种类型的对象在内存中的存储方式不同.值类型只需要一段单独的内存,用于存储实际的数据,引用类型需要两段内存:第一段存储实际的数据,它总是位于堆中,第二段是一个引用(即内存地址,不懂是不是就是直接寻址),指向数据在堆中的存放位置. 如果数据不是其他类型的成员,是独立的,比如方法代码块声明的临时变量int age ;如果是某一类型的成员,,

C#中值类型和引用类型

本文将介绍C#类型系统中的值类型和引用类型,以及两者之间的一些区别.同时,还会介绍一下装箱和拆箱操作. 值类型和引用类型 首先,我们看看在C#中哪些类型是值类型,哪些类型是引用类型. 值类型: 基础数据类型(string类型除外):包括整型.浮点型.十进制型.布尔型. 整型(sbyte.byte.char.short.ushort.int.uint.long.ulong ) 浮点型(float 和 double ) 十进制型(decimal ) 布尔型(bool ) 结构类型(struct) 枚

04.C#类型系统、值类型和引用类型(二章2.2-2.3)

今天要写的东西都是书中一些概念性的东西,就当抄笔记,以提问对话的方式将其写出来吧,说不定以后面试能有点谈资~~~ Q1.C#1系统类型包含哪三点特性? A1.C#1类型系统是静态的.显式的和安全的. Q2.为什么称为静态类型? A2.静态类型是用来描述表达式在编译时的类型,当声明一个类型的变量时,不能将变量指向其它类型的对象. Q3.显式类型和隐式类型的区别? A3.显式类型和隐式类型只有静态类型中的语言才有意义.显式类型需要显式声明一个变量的类型,而隐式类型则将类型的判断责任推给编译器,但是在

现金与存折---值类型和引用类型

在软考的时候也接触过值类型和引用类型,那时候应付做题还是可以的,可是考完之后再突然面对这两个词汇,又觉得迷茫无措了.现在想想,还是实践吧,当时只是简单的了解了其原理,没有用代码来实现,所以只能算是初步的,暂时的了解.这篇文章就是为了弥补初步的遗憾,进行深一步的学习. 理论联系实践,才是对现实的超越.就像门和钥匙一样,完美结合才有防窃和安全之功效.所以,该篇文章的主要思路也是从理论和实践两个方面分别对"值类型和引用类型"进行详细阐述. --------------------------

【转】C#详解值类型和引用类型区别

通用类型系统 值类型 引用类型 值类型和引用类型在内存中的部署 1 数组 2 类型嵌套 辨明值类型和引用类型的使用场合 5 值类型和引用类型的区别小结 首先,什么是值类型,什么是引用类型? 在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中. 值类型(value type):byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型.值类型变量声明后,不管是否已经赋值,编译器为其分配内