C#基础_值类型和引用类型

C#将数据类型分为两种:值类型和引用类型。

这两种类型存储在内存的不同的地方:值类型存储在堆栈(Stack)中,而引用类型则存储在托管堆(managed)上。区分类型是值类型还是引用类型非常重要,这会造成不同的结果。在了解上面的知识之前首先应该弄清楚两个问题。

1.什么是栈?

栈可以理解为一种内存结构,它是先进后出的,就像桶装薯片一样,放进去的时候是一个接一个压进去的,在这里我们成为压栈,而要去拿的时候都是从最后一个进去的开始那。这就是它的特点:先进后出。

在语言中比如我们的程序入口Main方法中有一个GetMoney的方法。它的执行顺序就是先将Main方法压栈再将GetMoney方法压栈,那么执行的时候就是先执行GetMoney方法,执行完毕之后再执行Main方法,这也是栈的应用。

A:栈只能在一端进行操作,也就是栈顶,因为栈底是封闭的。

B:栈不需要开发人员进行管理内存,压栈的时候自行分配内存,出栈的时候自行清理内存。

C:栈的可用空间不大,因此它的执行效率很高,它不能动态请求内存,只能够为内存大小确定的数据分配内存。

2.什么是堆?

A:堆在C#中用于存储实例对象,对象可能会有很多数据,因此它的第一个特性就是:容量大能够存储很多数据,而且能够动态的分配存储空间。

B:栈我们已经知道了只能在栈顶一端进行操作,但堆就不一样了,它可以随意的存取,非常灵活。

C:事情都是有双面性的,堆的内存空间既然大能够存储的数据多,那么它的执行效率肯定是没有栈高的。

一:那么值类型和引用类型到底是怎么样在内存中分配的?

A:对于值类型来说:其变量对应的值是存放在栈中。如果该值类型的实例作为类型的成员,而该实例作为引用类型的一部分的时候,则它被创建在GC堆上。

在这里插入图片描述

B:对于引用类型来说:一块空间被分配在堆上,存储应用本身的数据,而另外一块则被分配在栈上,存储对 堆上数据的引用(其实就是内存地址 也叫做指针)例如Person p = new Person();这里可以分为两部分来理解。Person p相当于定义了对象的引用,也就是记录了对象实例的指针,而不是对象本身。这个引用存储在栈中,当没有使用p = new Person()的时候,引用本身为空也就是相当于指针没有指向任何位置;当p = new Person()后,才根据真正的对象的大小动态的在堆中分配空间给对象实例,然后会将实例的引用赋值给p。到这里才算是完成了一个对象的实例化。如下图:

在这里插入图片描述

在C#中还有一种概念叫按值传递和按引用传递?

1.什么时按值传递?我们先来看两段代码。

    public struct GameStateStruct
    {
        public int a { get; set; }
        public int b { get; set; }
    }
    public class GameStateClass
    {
        public int a { get; set; }
        public int b { get; set; }
    }

1.定义了一个结构体 名称为GameStateStruct

2.定义了一个类 名称为GameStateClass

3.在Main方法中操作如下

var tempStract1 = new GameStateStruct();
            tempStract1.a = 1;
            tempStract1.b = 1;
            //var tempClass1 = new GameStateClass();
            var tempStract2 = tempStract1;
            tempStract1.a = 2;
            tempStract1.b = 2;
            Console.WriteLine(tempStract1.a + " :" + tempStract1.b);
            Console.WriteLine(tempStract2.a + " :" + tempStract2.b);

4.结果如下:2:2 1:1 那就说明了:对于值类型而言会在栈上重新开辟一块新的空间,将值复制过去。tempStract1和tempStract2是相互独立的,在上述中改变了tempStract1的值并不会影响到tempStract2.

2 什么是引用传递?

            var tempClass1 = new GameStateClass();
            tempClass1.a = 1;
            tempClass1.b = 1;
            var tempClass2 = tempClass1;
            tempClass1.a = 2;
            tempClass1.b = 2;
            Console.WriteLine(tempClass1.a + " :" + tempClass1.b);
            Console.WriteLine(tempClass2.a + " :" + tempClass2.b);

结果如下:2:2 2:2 那就说明了:对于引用类型而言也会在栈上重新开辟一块新的空间,但注意这里和值传递的不同的是:这里会将栈上的引用复制到新开辟的空间,引用,引用,引用!为什么可以这么说呢?因为从打印结果来看我们只是改变了tempClass1的a和b的值,并没有去改变tempClass2的a和b的值,但是结果却是一样的。说明了改变tempClass1的值会对tempClass2的值产生影响。如下图所示:

在这里插入图片描述

从上图就可以很好的解释为什么。当我们执行完var tempClass1 = new GameStateClass();时候如图中黑线所示,在栈中会开辟一个空间存tempClass1对 堆上new的GameStateClass对象的引用(也可以说为指针)。然后当tempClass2 = tempClass1执行的时候会在栈中重新开辟一个空间存tempClass2然后将tempClass1的引用赋值给他,也就是说tempClass2存储的也是指向堆的new的GameStateClass的对象。为了形象化这里的箭头可以看做引用。最后当我们通过tempClass1去修改对象的数据之后,因为tempClass1和tempClass2都是指向同一个对象,所以我们通过tempClass2去访问对象的属性a和b的时候,结果也为一样的。这就验证了上面的打印结果为什么时2:2 2:2了。

综上所述:值传递和引用传递的区别就是:值类型是复制数据本身,形成相互独立的数据存储区,引用类型是复制引用(指针),存储的是引用,引用指向同一对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值