【深入理解CLR 六】基元类型、引用类型和值类型
最近工作的事情比较忙,导致CLR很久没有更新了,恰巧周五听了涛涛的关于GC和内存管理的技术分享,想了下自己对CLR的学习得跟上,另外之前武哥推荐了一本书叫《码农翻身》,是一个IBM架构师写的,读了几页觉的非常有趣,身感学习笔记不能再这么干巴巴的记了,只是知识点概念的梳理,仅仅自己再看到时方便搞懂,但别人很难看懂,那既然是博客嘛,就是让大家看了会觉的很有趣,并且有点儿收获,这样才能起到知识分享的作用嘛,所以从这篇博客开始,不光要注重质量,还要注重趣味性。本篇是第一篇试水啊,求轻喷。如果有错的地方一定指正哈,共同进步。
#背景介绍
孩子,请把悲悯的目光在我这个体弱多病的托钵僧身上多停留一会儿吧**(注:托钵僧的想法来自于《成吉思汗》这本书,苏联人笔下的托钵僧曾环游世界,见证过世界各个帝国的兴衰),我能从里边汲取些力量来讲述我的传奇经历。在我漫长的一生中(此时为),我曾环游世界。见过不同的人种(注:各种操作系统,MAC OS(白种人),Windows(黄种人),Linux(黑种人),当然还有少数种),到过不同的版图(注:各种品牌的设备,个人PC ,移动端,服务器等)**,见证过各式各样帝国的兴衰(编程语言),现在我足够年迈了,精力也赶不上从前了,孩子,请你用灵巧的双手在终将化为腐朽的羊皮卷上帮我记录我这一生的见闻吧,在它腐烂之前我希望可以有足够的时间在程序员间传颂,无论他们属于哪个人种,无论他们属于哪个工种,无论他们生活在哪片版图,无论他们曾效力于哪个帝国或终将倒下在哪场战役里,我希望在他们战斗的间隙能够在古老的传说中得到安憩,在美丽的睡梦中可以体会沧海桑田的壮美。
孩子,我出生的年代(计算机历47年),正是微软鼎盛时期,而当我准备接受帝国士兵的训练时(码农新兵营)却换了天下,JAVA帝国蒸蒸日上(计算机历69年),于是我在各个帝国的新兵招募中选择了JAVA帝国,毕竟毕业以后好找工作啊。当时的世界格局是这样的:面向对象编程的语言存在两大主力阵营,分别是JAVA帝国和.Net帝国,两个帝国各有特点,JAVA帝国种族单一,团结一致,骁勇善战,可以轻易的在各种环境中冲杀,而且有一套完整的开发的士兵成长生态体系,士兵们不仅能学到最新的帝国技术,还会依靠自己的聪明才智,将技术改进更新。所以在面向对象的Web开发中已经占据了大批领土。而.Net帝国则由于各级官员恪守传统,想指着操作系统大赚一笔,所以从来舍不得公开先进技术来提高士兵的个人素质,士兵也就没什么心思想着进步,给啥工具干啥活,做一天和尚敲一天钟,帝国日益衰败。好在帝国觉醒的早,公开了跨平台秘密武器.net core的技术细节,还大举收购了技术交易的黑市GitHub,准备依靠强大的影响力来重新构建生态!俗话说30年河东,30年河西,世界格局的变化谁都料想不到,这期间,当我从3年的新兵训练营毕业之后,从JAVA帝国叛逃出来并加入了.Net帝国,来到了.Net帝国之后我年轻气盛,你想啊,孩子,那会儿我和你一般大小,刚满24岁(计算机历72年),充满了好奇心,对于帝国的为何只让黄种人使用并且一直不公开技术细节搞不明白,所以我从维持帝国运转的基础—CLR开始研究,试图搞懂一些真相。
#材料结构
刚刚抵达.Net帝国首都的时候,我发现了一个奇怪的现象,这里的原材料被分为三类**(基元类型,值类型,引用类型),而在Java帝国我只见过两类(值类型和引用类型)**,引用类型没什么好说的,都是对象被创建在堆上,有一个栈上的变量持有堆的引用。而值类型就不一样了,由于.Net帝国最初建国时的宏伟目标(兼荣其它以前的各个部落),所以在.Net帝国有结构体(或者枚举)这种特殊材料,结构体(枚举)当做了值类型。那么基元类型的材料是个什么东西呢?这要从材料的操作说起了。
##特种材料—基元类型
为了兼容以前一些部落,不同于java,C#的值类型不仅包括枚举,也包括结构体。
###基元类型定义
所以我们熟知的值类型很多是结构体,例如System.Int32。但是直接用结构体写起来很麻烦。例如我要声明一个System.Int32的值类型,我需要这么对材料操作:
System.Int32 a = new System.Int32();
可是很多时候写类的全名太麻烦了,如果有一种材料不用new,也不用让我记住这么长的类名就好了,于是帝国数据研发实验室研发了一种新型材料,这种材料说来原理也简单,其实就是简单的对类的映射,上述代码可以简化为:
int a =0;
其实相当于在文件头做了这样的操作:
using int =System.Int32
当然,基元类型不一定光针对值类型的材料哈,对引用类型的也有一定支持,例如:
基元类型 | FCL类型 |
---|---|
object | System.Object |
string | System.String |
之前一直纠结string和String有什么区别,在这里豁然开朗, 所以基元类型是编译器直接支持的数据类型,是直接映射到FCL(FrameWork)中存在的类型。是一种简化写法以及CLR特殊处理的一种类型。以下是一些对应关系。
###CLR对基元类型的特殊处理
说到CLR对基元类型的特殊处理呢(注意,特殊处理是只针对基元类型名称,也包括它对应的映射类),主要体现在类型转换和字面值处理上。
####基元类型可以互转
依据上一篇博客提出的观点,两种类型必须存在相互派生关系才能进行类型转换,但你会发现,你这么操作这种材料也不会有什么问题:
int i = 5; //32位值
long l = i; //隐式转型为64位
System.Int32和System.64相互之间可不存在相互派生关系哈,那他们是怎么互转的呢(以前在新兵训练营的时候一直认为值类型这么互转是理所应当的,但不知道其实他们违背了转型原则,这里终于知道为什么了,因为有特殊处理),
####字面值可被看成基元类型实例
首先解释下字面值的概念,其实字面值就是常量。例如123可以被当成基元类型System.Int32的实例,可以直接当成对象用,例如123.toString()
这一点平时用的时候没有体会,这里才知道为啥数值也能被当成对象用,而根部无需变量名。
####编译器早期优化
如果表达式由字面值构成,编译器在编译时就能完成表达式求值,这可是大大提高了CLR的执行性能呀(你要想程序集的加载和运算会是多么复杂一个过程,各种方法栈的调用,这样真的省事儿不少)例如:
bool flag = false;//编译后flag为0
int x = 100+20+30;//编译后x为150
string s = "abc"+"def";//编译后为"abcdef"
###checked和unchecked基元类型操作
基元类型的很多操作都会导致溢出,例如:
byte b = 100;
b = (byte) (b+200);
要知道byte是8位精度的盒子,只有128个格子,可装不下300个格子的货物,所以这里一定会溢出,那溢出之后呢?
####全局控制开关
直接打开溢出检查开关/checked+
,这样所有代码在生成的时候都将进行溢出检查,如果发生溢出,CLR跑出OverflowException异常。VS在属性–生成–高级里设置
####使用检查块儿
可以将想要检查部分的代码单独用检查块包起来,例如这样,相当于给数据原材料做标记
checked
{
byte b = 100;
b = (byte)(b + 200);
}
事实上使用了溢出检查块,不加强制转换也可以啦
checked
{
byte b = 100;
b += 200);
}
####直接放到语句里
byte b = 100;
b = checked((byte)(b + 200)); //会报溢出错误
但换个方式,就不会报,因为byte在和int类型计算的时候会被当成隐式转换为int,所以
byte b = 100;
b = (byte)(checked)(b + 200); //不会报溢出错误
的时候,checked的类型是int,而300达不到int类型的溢出,所以检查之后不会抛出异常,虽然之后会被截断。
###不推荐使用基元类型
这个不是我说的,其实要我选,我还是选择基元类型,多方便啊,但帝国掌管CLR的大臣jeffrey坚决不赞同使用基元类型名称,我将一一反驳(虽然我权小势微):
- 使用基元类型久了以后,有些人认为int在32位机器上映射System.Int32,在64位机器上映射System.Int64,事实上int永远对应System.Int32。我反驳:我可不会这么认为
- C#long映射到System.Int64,在其他语言中不是。我反驳:抱歉,我不care,我只用C#
##可塑材料—引用类型
System.Object-------若干类 //引用类型
###前提条件
使用引用类型有4个前提条件:
- 内存必须从托管堆分配
- 堆上分配的每个对象都有些额外成员,这些成员必须被初始化
- 对象中的其它字节(为字段而设)总是设为0
- 从托管堆分配对象的时候可能强制执行一次垃圾回收
##钢材—值类型
System.Object-------System.ValueType-----System.Enum //枚举类型
System.Object-------System.ValueType-----struct结构体结构 //值类型
###前提条件
要成为值类型有3个条件:
- 类型具有基元类型行为。也就是希望该类型不可变。
- 类型不继承
- 类型不派生
注意,所有值类型都是隐式密封的!特大好处在于,**一旦定义了该类型的的一个实例的方法不再活动,也就是局部变量所在的方法,为它们分配的存储就会被释放!**再也不用等着垃圾回收喽。
##值类型和引用类型区别
当然,熟悉面向对象的程序员都知道二者的区别,我这里就简单介绍下,不多说了,毕竟下一篇的装箱拆箱才是重点。先看下代码清单:
这时候,代码在内存中是这么分配的:
不过这里一定要注意:值类型虽然不在堆上分配,但一定要初始化,也就是说使用new关键字。至于new可不一定是引用类型的专用哦。CLR会识别你到底是值类型还是引用类型的,这个不用担心。
也许是写的有点儿匆忙,没有好好规划,怎么感觉趣味性没上来呢,也有可能是缺乏对全局的把控吧,期待下一篇能有所改进吧。学习笔记也得单章拆分短期迭代啊,要不然又懒得学习懒得写了。