C#值类型、引用类型(堆和栈)---知识点10

栈和堆

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

栈(Stack)

  • 栈是一种只能先进后出的内存结构。
  • 存放函数的参数、局部变量、返回数据等值,由编译器自动释放
  • 栈只能在一端对数据进行操作,也就是栈顶端进行操作。
  • 栈也是一种内存自我管理的结构,压栈自动分配内存,出栈自动清空所占内存
  • 栈中的内存不能动态请求,只能为大小确定的数据分配内存,灵活性不高,但是栈的执行效率很高
  • 栈的可用空间并不大,所以我们在操作分配到栈上的数据时要注意数据的大小带来的影响

堆(Heap)

  • 在c里面叫堆,在c#里面其实叫托管堆。托管堆不同于堆,它是由CLR(公共语言运行库(Common Language Runtime))管理,当堆中满了之后,会自动清理堆中的垃圾。所以,做为.net开发,我们不需要关心内存释放的问题。
  • 相比栈只能在一端操作,堆中的数据可以随意存取。
  • 能存储大量数据,而且堆能够动态分配存储空间
  • 但堆的结构使得堆的执行效率不如栈高,而且不能自动回收使用过的对象

概念

值类型直接存储其值,而引用类型存储对其值的引用

  • 值类型派生自 System.ValueType,System.ValueType又派生自System.Objcet
  • 引用类型派生自 System.Objcet
  • 在这里插入图片描述

值类型:

byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。
(decimal 关键字指示 128 位数据类型。 与浮点型相比,decimal 类型具有更高的精度和更小的范围,这使它适合于财务和货币计算。)

引用类型:

string 和 class、interface、delegate 、Dynamic 、Object 、数组统称为引用类型。
数组(派生于System.Array)

值类型特点

  • 值类型变量声明后,不管是否已经赋值,编译器为其分配内存。

  • 值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。例如:数组中的元素,引用类型中的值类型的字段,迭代器中的局部变量、闭包情况下匿名函数(lamda)中的局部变量。这些如果值类型分配在线程栈上,有可能出现线程栈中的方法已经调用结束,但是还会访问这些值的情况,可能随着被调用方法的返回而被清除掉。

引用类型特点

  • 引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
  • 引用类型的对象总是在进程堆中分配(动态分配)。
例子

实际上,对于数组:
TestType[] testTypes = new TestType[100];
如果TestType是值类型,则会一次在托管堆上为100个值类型的元素分配存储空间,并自动初始化这100个元素,将这100个元素存储到这块内存里。
如果TestType是引用类型,则会先在托管堆为testTypes分配一次空间,并且这时不会自动初始化任何元素(即testTypes[i]均为null)。等到以后有代码初始化某个元素的时候,这个引用类型元素的存储空间才会被分配在托管堆上。

区别

相同点:

  • 引用类型可以实现接口,值类型当中的结构体也可以实现接口;

  • 引用类型和值类型都继承自System.Object类。

1)范围方面

  • C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。

  • C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。

2)内存分配方面:

  • 数组的元素不管是引用类型还是值类型,都存储在托管堆上。

  • 引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。简称引用类型部署在托管推上。而值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实 例)存储;作为局部变量时,存储在栈上。(栈的内存是自动释放的,堆内存是.NET中会由GC来自动释放)

3)适用场合

  • 值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。

  • 引用类型可以派生出新的类型,而值类型不能,因为所有的值类型都是密封(seal)的;

  • 引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型,如 int? a = null; );

  • 引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。

  • 值得注意的是,引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较

using System;
using System.Collections.Generic;

namespace Lesson24_值和引用
{
    class Test
    {
        public static int TestI = 0;

        int b = 0;

        string str = "123";

        TestStrict ts = new TestStrict();
        public void Fun()
        {
            b = 1;
        }
    }

    struct TestStrict
    {
        public Test t;
        public int i;
    }

    class Program
    {
        static int b;
        static void Main(string[] args)
        {
            Console.WriteLine("值和引用");
            #region 知识回顾
            //值类型
            //无符号:byte,ushort,uint,ulong
            //有符号:sbyte,short,int,long
            //浮点数:float,double,decimal
            //特殊:char,bool
            //枚举:enum
            //结构体:struct

            //引用类型
            //string
            //数组
            //class
            //interface
            //委托

            //值类型和引用类型的本质区别
            //值的具体内容存在栈内存上
            //引用的具体内容存在堆内存上
            #endregion

            #region 问题一 如何判断 值类型和引用类型
            //F12进到类型的内部去查看
            //是class就是引用
            //是struct就是值
            int i = 12;
            string str = "123";
            #endregion

            #region 问题二 语句块
            //命名空间
            //   ↓
            //类、接口、结构体
            //   ↓
            //函数、属性、索引器、运算符重载等(类、接口、结构体)
            //   ↓
            //条件分支、循环

            //上层语句块:类、结构体
            //中层语句块:函数
            //底层的语句块: 条件分支 循环等

            //我们的逻辑代码写在哪里?
            //函数、条件分支、循环-中底层语句块中

            //我们的变量可以申明在哪里?
            //上、中、底都能申明变量
            //上层语句块中:成员变量
            //中、底层语句块中:临时变量
            #endregion

            #region 问题三 变量的生命周期
            //编程时大部分都是 临时变量
            //在中底层申明的临时变量(函数、条件分支、循环语句块等)
            //语句块执行结束 
            //没有被记录的对象将被回收或变成垃圾
            //值类型:被系统自动回收
            //引用类型:栈上用于存地址的房间被系统自动回收,堆中具体内容变成垃圾,待下次GC回收

            int i2 = 1;
            string str2 = "123";

            //{
            //    int b = 1;
            //}
            //Console.WriteLine(b);
            //while(true)
            //{
            //    int index = 1;
            //}

            //想要不被回收或者不变垃圾
            //必须将其记录下来
            //如何记录?
            //在更高层级记录或者
            //使用静态全局变量记录
            b = 0;
            if(true)
            {
                b = 1;
            }

            int c = 10;
            Test.TestI = c;

            //Game g = new Game();
            //while(true)
            //{

            //}
            #endregion

            #region 问题四 结构体中的值和引用
            //结构体本身是值类型
            //前提:该结构体没有做为其它类的成员
            //在结构体中的值,栈中存储值具体的内容
            //在结构体中的引用,堆中存储引用具体的内容

            //引用类型始终存储在堆中
            //真正通过结构体使用其中引用类型时只是顺藤摸瓜

            TestStrict ts = new TestStrict();
            #endregion

            #region 问题五 类中的值和引用
            //类本身是引用类型
            //在类中的值,堆中存储具体的值
            //在类中的引用,堆中存储具体的值

            //值类型跟着大哥走,引用类型一根筋
            Test t = new Test();
            #endregion

            #region 问题六 数组中的存储规则
            //数组本身是引用类型
            //值类型数组,堆中房间存具体内容
            //引用类型数组,堆中房间存地址
            int[] arrayInt = new int[5];
            object[] objs = new object[5];
            #endregion

            #region 问题七 结构体继承接口
            //利用里氏替换原则,用接口容器装载结构体存在装箱拆箱

            TestStruct obj1 = new TestStruct();
            obj1.Value = 1;
            Console.WriteLine(obj1.Value);
            TestStruct obj2 = obj1;
            obj2.Value = 2;
            Console.WriteLine(obj1.Value);
            Console.WriteLine(obj2.Value);

            ITest iObj1 = obj1;//装箱  value 1
            ITest iObj2 = iObj1;
            iObj2.Value = 99;
            Console.WriteLine(iObj1.Value);
            Console.WriteLine(iObj2.Value);

            TestStruct obj3 = (TestStruct)iObj1;//拆箱

            #endregion
        }
    }

    interface ITest
    {
        int Value
        {
            get;
            set;
        }
    }

    struct TestStruct : ITest
    {
        int value;
        public int Value 
        {
            get
            {
                return value;
            }
            set
            {
                this.value = value;
            }
        
        }
    }

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值