NET CLR via C#读书笔记 - 第五章 基元,引用和值类型
1 基元类型
1.1 概念
基元类型可以简单理解为编译器本身支持的数据类型
例如:System.Int32 a = new System.Int32(); //值默认初始化为0
由于编译器内部对此做了处理,我们可以简单的将语句修改为:
int a = 0;
编译器会将int在编译时映射为FCL中System.Int32类型
1.2 C#基元数据类型
C#基元类型 | FCL类型 | 是否符合CLS | 说明 |
---|---|---|---|
sbyte | System.SByte | false | 有符号8位值 |
byte | System.Byte | true | 无符号8位值 |
short | System.Int16 | true | 有符号16位值 |
ushort | System.UInt16 | false | 无符号16位值 |
int | System.Int32 | true | 有符号32位值 |
uint | System.UInt32 | false | 无符号32位值 |
long | System.Int64 | true | 有符号64位值 |
ulong | System.UInt64 | false | 无符号64位值 |
char | System.Char | true | 16位Unicode字符 |
float | System.Single | true | IEEE 32位浮点值 |
double | System.Double | true | IEEE 64位浮点值 |
bool | System.Boolean | true | false/true值 |
decimal | System.Decimal | true | 128位高精度浮点值,其中1位是符号位,96位是值本身(N),8位为比例因子(k),实际值为 ± N * (10 ^ k) |
string | System.String | true | 字符串(字符数组) |
object | System.Object | true | 所有类型的基类型 |
dynamic | System.Object | true | 对于CLR,dynamic与object完全一致,只是C#编译器在处理时会让dynamic定义的类型参与动态调度 |
1.3 基元类型的相关知识点
1.3.1 C#中允许基元类型执行安全的隐式转换
安全转换指的是转换完成后不丢失精度和数量级。
例如System,Int32向System.Int64进行转换,不需要显示转换,但是System.Int64向System,Int32则需要显示转换。
补充知识点:不同的编译器对于同一源代码的转换会产生不同的结果,例如将 6.8f 转换为 System.Int32 类型时,有些编译器是向上取整,有些编译器是向下取整,C#编译器永远是执行向下取整,也就是对6.8f进行截断处理,只保留6这个整数部分。
1.3.2 checked和unchecked
C#中基元类型的计算很多时候都会出现溢出情况,C#中支持使用checked和unchecked自定义是否检查溢出情况,C#编译器一般是默认检查溢出的,书写方式如下:
Byte b = 100;
b = checked((Byte)(b + 200)); //会抛出overflowException异常
也可以对某一代码块进行溢出检查:
checked{
Byte b = 100;
b = (Byte)(b + 200);
}
特例说明:对基元类型System.Decimal使用checked和unchecked操作符是无效的,因为CLR本身不知道如何去处理System.Decimal值得IL指令,需要借助System.Decimal类型定义的Add等操作函数以及重载的+,-,*,/来进行实际的运算,所以System.Decimal类型运算相比于其它基元类型要慢,而且出现溢出时一定会抛出overflowException异常。
2 引用类型和值类型
2.1 引用类型和值类型定义
所有值类型都从System.ValueType派生,使用struct关键字定义。
所有引用类型都使用class关键字定义。
2.2 引用类型和值类型相关知识点
1、引用类型是从托管堆分配内存,值类型是在线程栈上分配空间。
事实上引用类型使用过程中会执行以下四个步骤:
① 从托管堆分配内存
② 对象分配时都需要额外分配成员,且分配的额外成员必须初始化
③ 对象的其它字节也必须初始化为0
④ 从托管堆分配内存时,可能会强制执行一次垃圾回收机制
值类型不受以上四个部分的困扰,值类型在线程栈上分配空间,且实例的字段也是直接存储,不需要分配额外的成员进行控制。值类型的存在减轻了托管堆的压力,而且显著减少垃圾回收次数,从而有效的提高程序的执行效率。
2、所有值类型都是隐式密封,避免作为其它类型(引用类型和值类型)的基类型。
3、值类型的装箱和拆箱机制
装箱:将值类型的实例转化为引用类型。
过程如下:
① 根据值类型的大小以及需要分配的额外成员(类型对象指针和同步块索引)在托管堆上分配内存。
② 将值类型的字段复制到第一步中分配的对内存中。
③ 返回对象地址(也就是分配堆的内存的地址)。
拆箱:将已装箱的值类型对象转化为未装箱的值类型。
过程如下:
① 获取已装箱的值类型对象各个字段的地址。
② 将各个字段包含的值复制到基于栈的值类型实例中。
结合上述说明,值类型装箱的代价要比拆箱的代价大,但两者都会对程序运行速度和内存资源造成影响,必要时要检查书写的代码是否存在频繁的装拆箱,并着手进行优化。
以上内容为对《NET CLR via C#(第四版)》第五章内容的阅读笔记,只记录其中核心部分内容,书中还仔细描述了使用接口更改已装箱值,对象的同一性和一致性,对象哈希码以及dynamic等话题,如需要详细阅读请自行查阅本书第五章内容。