漫谈值类型和引用类型

一.前言

从这个简单程序的输出结果,你想到了什么?是不是与你心中想的结果不一致?是不是觉得输出的结果应该为:i is 1,o is 8,o2 is 8

二.程序执行前

                            

图 2

我们都知道,每一个方法在执行前,操作系统会给方法内每个变量分配内存空间。从图2中就可以看出,在执行前各变量(i,o,o2)已分配了内存,且各自都有初始值。

从图中,可以发现变量i和变量o,o2有些许不同。变量i在内存中存储的值和程序中的值是一样的,都是0;变量o,o2在内存中存储的值和程序中的值不一样,内存中存储的值是一个地址(0x00000000),程序中的值是null,那变量o,o2的null值存储在哪呢?为什么变量i和变量o,o2会有如此大的不同呢?

我们都知道,C#有两大类型:值类类型和引用类型。图2中int属于值类型,object属于引用类型。接下来,介绍一下值类型和引用类型:

1.值类型的值存储在内存栈上,引用类型的值存储在内存堆中。

园中有很多博文这么描述,我用程序验证了一下全局的值类型变量的值,静态的值类型变量的值,引用类型实例中值类型成员的值,如下图3

图 3

从图中,可以看出变量(j,o,seg,st)的值应该是在同一个存储区域中,而变量(gi)是在另外一个存储区域中。引用类型Student的成员Age的地址还未分配。所以说值类型的值存储在内存栈上是不准确的。

查找了一些资料,内存格局分为四个区:

1)全局数据区:存放全局变量,静态变量,常量的值

2)代码区:存放程序代码

3)栈区:存放为运行而分配的局部变量,参数等

4)堆区:自由存储区。

更为准确的说,方法体内的值类型变量的值存储在内存栈上,引用类型变量的值存储在内存堆上。由于对象实例是引用类型变量的值,而对象实例成员只是对象实例的一部分,所以其随对象实例整个存储在内存堆上。

或许眼尖的园友发现了,上面那句话还是不对,结构体StructEg的引用类型成员Name的数据就没有存储在内存栈上。从图3看,结构体变量seg的数据分成两部分,值类型成员数据存储在内存栈上,引用类型成员数据存储在内存堆上。

所以确切的说:方法体内的预定义的值类型(int,bool,char)变量的数据存储在内存栈上,引用类型变量的值存储在托管堆中,结构体的值类型成员的值存储在内存栈上,结构体的引用类型成员的值存储在内存堆中。(下面介绍的值类型基本是预定义的值类型和只包含值类型成员的结构体,一般包含引用类型成员的都定义成类)

2. 值类型变量直接存储数据,而引用类型变量则存储对数据的引用

这句话怎么理解呢?这句话中关键词是”存储”,其实还是在描述程序中的变量在内存栈中的表现。

值类型变量在内存栈中存储的是其在程序中的变量值,引用类型变量在内存栈中存储的是其程序中的值在内存堆中的引用。(当然值类型变量和引用类型变量都是方法体内的局部变量或参数)

3.引用类型变量赋值过程

1)分配内存堆空间:我们都知道要存储数据,首先得申请内存空间。引用类型变量在new实例化时,系统在内存堆中分配空间。

2)更新地址:把引用类型变量在内存栈中存储的值更新成新的值(新值为新分配的内存堆的首地址)。至此,引用类型变量指向了新的内存空间。

3)填充值:把初始化值填充到内存堆中。

可能有些园友会说,了解这个有什么意义呢?那我就简单的说一个现象:

1)在学习方法理论时,传参会有这样的描述:值类型按值传递,传递的是对象的副本,对已调用方法中的对象的更改对原始对象无影响;引用类型的对象按值传递传递的是对对象的引用,使用此引用更改对象的成员,此更改将影响原始对象。

其实,这里究其原理,就是因为值类型与引用类型的值的不同存储位置,来描述其传参后的影响。

所以,有时我们为了影响值类型实参的值,而在形参前面加ref或out;有时我们为了不影响引用类型实参的值,而采用深拷贝的方式传递参数值。

理论是为了指导实践,当了解了其原理,在实践时,我们才会显得踏实。

三.执行变量i赋值

从图中可以看出,执行后,值类型变量在内存中存储的值和其在程序中的值是一样的,都是1。

四.执行object o=i;

图 5

从图中可以看出,引用类型变量o的值变成1了,在内存栈中存储的值更新成新地址了。通过前面分析,我们知道变量o指向了1的新地址。值类型变量的值存储在内存栈中,引用类型变量的值存储在内存堆中,内存栈中的值是如何到内存堆中的?这就是本节要介绍的第二个重要概念,装箱和拆箱。

4.1.装箱

1.装箱:装箱是把值类型到object类型或值类型到其实现的接口的隐式转换。

园中很多博文介绍:值类型转换为引用类型,就叫装箱。我觉得这表述不太准确,如下图6

图 6

从图6中可以看出,值类型不能随意的转换为引用类型,它只能隐式转换为以下两种引用类型:

1)object类型;

2)值类型实现的接口

2.装箱的过程

前面已介绍了引用类型变量赋值过程了,装箱步骤也类似:

1)分配新的内存空间

2)更改地址

3)填充值:从值类型变量处拷贝一份值,存储到新分配的内存堆中。

4.2.拆箱

1.拆箱:从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。

2.拆箱过程

1)检查对象实例,以确保它是给定值类型的装箱值。若不能显式转换,则抛异常。

意思是:装箱时的值类型和拆箱时的值类型要完全一致(哪怕类型兼容也不行,如下图7中的装箱前的类型是short,拆箱后的类型是int,就会产生异常)。如图7

图 7

2)验证成功后,复制实例的值到值类型变量中

相对于简单的赋值而言,装箱和拆箱过程需要进行大量的计算,所以其对性能会有较大的损耗。特别装箱时,要创建新的对象实例,要在内存堆上分配新的内存空间,在分配新的内存空间时,可能会引起垃圾回收(垃圾回收对性能损耗非常大,具体垃圾回收为什么会有很大的性能损耗,网上相关介绍很多,在此不做介绍)。

或许有园友会说,平时装箱/拆箱操作不多,其实在你不经意间,存在很多装箱操作

1)string s=string.Format(“{0}”,i);//i为值类型数据---典型的字符串格式化

四.执行object o2=o;

图 8

图 9

无论是值类型变量赋值还是引用类型变量赋值,都是把数据复制一份,然后赋给另一个变量。只是引用类型变量在内存栈中存储的是其值在内存堆中的地址。所以引用类型变量间赋值,就使两个变量指向了同一个内存堆空间。如上图8,图9

五.执行o=8

此时,或许有人会说,这句不是表示对引用类型变量进行操作吗?赋值了8后,它在内存堆内的值应该是8了,由于o2与o都指向内存堆内的同一个地址,所以o2的值也应该也是8。

呵呵,请注意,8是值类型,o是引用类型,类型不一样,要进行装箱操作,装箱的过程中会创建新的实例分配新的内存空间。所以引用类型变量o指向了新的内存堆空间了,由于引用类型变量o2没有做任何操作,所以此时引用类型变量o和o2在内存栈中存储的地址不一样了,指向的内存堆地址也不一样了,所以它们的值也就不一样了。如下图10,图11

图 10

图 11

那如何让o的值改变,o2的值也同时变化,就要改变o对应的内存堆内的值。

六.最后执行Console.WriteLine("i is " + i.ToString() + ",o is " + o.ToString() + ",o2 is " + o2.ToString());

所以最后结果的值是:i is 1,o is 8,o2 is 1

七.总结:

通篇通过一则简短的赋值程序,介绍了

1)C#两大类型:值类型与引用类型

2)值类型与引用类型互相赋值,引出的装箱、拆箱操作

其中简要介绍了装箱操作会有比较大的性能损耗,特别是垃圾回收。

最后,通过两张图来简要概括下本篇博文的内容:

1)C#两大类型:

2)变量赋值

原文地址:https://www.cnblogs.com/jingzhe2004/p/11828766.html

时间: 2024-10-07 16:50:49

漫谈值类型和引用类型的相关文章

定义类+类实例化+属性+构造函数+匿名类型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 统称为值类型.值类型变量声明后,不管是否已经赋值,编译器为其分配内