上一章的例子中,有留意的朋友,应该注意到了我们在计算矩形面积的时候,定义了如下两个变量,如下图:
一个是名为length,类型为double的变量;
一个是名为width,类型也为double的变量。
那为什么我们要定义double类型的变量呢?什么是double类型呢?
为了解释这个问题,我们还是回到上面的例子中,首先我们知道我们例子的目的是求矩形的面积,矩形的面积计算公式是:长 * 宽,也就是上面的 length * width。因为矩形的长宽,是存在小数的,所以一般情况我们都用double类型来定义。
那有朋友可能会问了,如果计算一个人的年龄?记录金额?记录一段文字我们可以用什么类型呢?回答这个问题之前,我们先看下面的表格:
上述表格中的类型,我们统一都叫做值类型。
值类型的变量是直接存储数据的,在申明后,不管是否赋值,编译器都会为其在内存中分配存储资源,具体位置为内存的栈上,由于尚未赋值,那么按照上述表格,它的初始值就是默认值,同样以上面的例子我们看下:
可以看到在开始,我们申明了两个double的变量,length 以及 width。这时候,按照我们的说法,只是做了申明,但是赋值是在Acceptdetails方法中,原本可以看到,这该方法内,我们对变量赋了值,其中length = 4.5,width = 3.5,之后计算面积的时候就是:4.5 * 3.5。
但是此时我们把方法中的赋值注释了,也就是说,这两个变量我们只做了申明没有做赋值,那么再次执行程序的时候发现,都是0,也就是我们表格中看到的默认值。
除了值类型外,还有引用类型。引用类型主要包括:object、dynamic 和 string。
值类型和引用类型最大的一个区别就是存储的空间以及方式不同。
举个例子,在申明一个引用类型class的时候,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
也就是说,值类型和引用类型,看似都保存在内存的栈中,但是,值类型,比如length = 4.5,那么4.5这个值就是存储在栈中。
而引用类型比如:string text = “baidu.com”,其实在栈中,存储的并非是:baidu.com这个值,真正存储这个值得地方是内存的堆中,而在栈中,存储的是一个指针,这个指针就是记录了baidu.com这个值在堆中的位置。
举个例子,内存就像一栋大楼,有A,B两个人。现在有个人来找他们,其中A就在传达室我们就找到他了,但是B,我们在传达室没找大本人,然而我们知道他住在201房间,那么我们间接通过传达室告诉我们的地址201,最终在201找到了他。这种情况,我们把A叫做值类型,B叫做引用类型。我们在传达室(栈)直接找到了A,但是在传达室(栈)我们只找到了B的房号,然而最终也是在二楼的位置(堆)找到了B,最终我们还是都成功找到了A和B。
到这里我们基本的值类型和引用类型都介绍完了,包括他们的不同点。但是有些细心的朋友会发现,他们有很多相同的地方,比如我们看下面的例子:
我们把double类型用object类型替换了,发现也能得到我们想要的结果,不同点在于,申明的时候,我们类型是object,然后在赋值的时候,我们也能令length=4.5,width=3.5,然后计算面积的时候,我们用了一个转换:Convert.ToDouble()之后,也能得到我们想要的结果。
在这个例子中,不同的地方有两处,一处是申明,用object替换double,一处在计算的时候做了一个转换:把object 转化为 double。(这里之所以要做转换,是因为object没有 加减乘除的方法)
虽然在大多时候,看起来过程稍微不同,但是结果却是相同,没什么问题,但是中间却出现了两处数据转换。
第一处是:将double类型的数字,4.5和3.5赋值给了object,这个过程我们称之为:装箱;
第二处是:将object类型,通过Convert.ToDouble(),转化为double类型,我们称之为:拆箱。
因为前面说了值类型和引用类型的存储方式的区别,所以可以显而易见的是,在拆装箱过程中,存在堆栈之间的内存分配和销毁和引用,那么随之而来必然存在内存性能上的损耗,这个能不能接受,如何避免就看我们实际场景中的需求了。
最后的总结,我们再来看下除了Convert.ToDouble()这种类型转换之外还有哪些类型转换:
好了,本章的内容基本就到这里了,有兴趣的朋友欢迎在评论区讨论。