基元类型
书上一开头就说了一个概念
编译器直接支持的数据类型称为基元类型(primitive type)。
以下是基元类型.
C# Primitive Typ | FCL Type | CLS-Compliant |
sbyte | System.SBte | NO |
byte | System.Byte | YES |
short | System.Int16 | YES |
ushort | System.UInt16 | NO |
int | System.Int32 | YES |
uint | System.UInt32 | NO |
long | System.Int64 | YES |
ulong | System.UInt64 | NO |
char | System.Char | YES |
float | System.Single | YES |
double | System.Double | YES |
decimal | System.Decimal | YES |
object | System.Object | YES |
string | System.String | YES |
和基元类型对应的 就是Class 或者 复杂类型。
这些经常使用的基元类型,所以有一些简化的语法去操作他.
1 int i = 0; 2 Console.WriteLine(i); 3 Console.ReadKey();
.entrypoint // 代码大小 15 (0xf) .maxstack 1 .locals init ([0] int32 i) IL_0000: ldc.i4.0 IL_0001: stloc.0 IL_0002: ldloc.0 IL_0003: call void [mscorlib]System.Console::WriteLine(int32) IL_0008: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() IL_000d: pop IL_000e: ret
IL_0000 将所提供的 int32 类型的值作为 int32 推送到计算堆栈上。
IL_0001 从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中
IL_0002 将索引 0 处的局部变量加载到计算堆栈上。
同样。
1 System.Int32 i = new System.Int32(); 2 Console.WriteLine(i); 3 Console.ReadKey();
生成的IL也一样。 就不贴了.
所以你可以认为
1 int 2 string
就是语法糖. 其实最后就是生成的一样的代码.
为了方便你编码.
比如还有
1 int i = 0; 2 Int64 l = i; 3 Console.WriteLine(l); 4 Console.ReadKey();
1 int i = 0; 2 Int64 l = (Int64)i; 3 Console.WriteLine(l); 4 Console.ReadKey();
1 .maxstack 1 2 .locals init ([0] int32 i, 3 [1] int64 l) 4 IL_0000: ldc.i4.0 5 IL_0001: stloc.0 6 IL_0002: ldloc.0 7 IL_0003: conv.i8 8 IL_0004: stloc.1 9 IL_0005: ldloc.1 10 IL_0006: call void [mscorlib]System.Console::WriteLine(int64) 11 IL_000b: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() 12 IL_0010: pop 13 IL_0011: ret
隐式转换。 不用你再去写什么 (int64)或者 Convert.ToInt64() 之类的.
不过他也有限制。只有在确认不会发生数据丢失的情况下,才允许隐式转换.
比如 从小的转向大的. int32 转 int64
否则
错误 3 无法将类型“long”隐式转换为“int”。存在一个显式转换(是否缺少强制转换?) 45 24 EFTest
本书作者不建议使用这些语法糖。
理由无外物 会让你不清楚 究竟做了什么 ,和其他编程语言标准不一致等等。
我当然完全无视这些.
Checked & UnChecked
checked 和 unchecked关键字用来限定检查或者不检查数学运算溢出的
如果使用了checked发生数学运算溢出时会抛出OverflowException
如果使用了unchecked则不会检查溢出,算错了也不会报错。
首先在VS中,Checked 应该是默认打开的
生成就已经报错了.
1 int a = unchecked( int.MaxValue * 2);
a = -2
unchecked 溢出后会生成一个特定的数。 具体算法不清楚. 特定场景比如你就需要一个不重复的数值。 那就可以用他.
对于这两个指令。应用场景实在太小了. 就不深入研究了.
引用类型和值类型
1. 什么是值类型
System.ValueType 派生的都是值类型
2. 什么是引用类型
在文档中查看一个类型时,任何称为“类”的类型都是引用类型
引用类型
内存必须从托管堆上分配
堆上分配的每个对象都有一些额外的成员,这些成员必须初始化
对象中的其他字节(为字段而设)总是设为零
从托管堆上分配一个对象时,可能强制执行一次垃圾收集操作
值类型
值类型的实例一般在线程栈上分配
在代表值类型实例的一个变量中,并不包含一个指向实例的指针。相反,变量中包含了实例本身的字段。
由于变量已经包含了实例的字段,所以为了操作实例中的字段,不再需要提领一个指针。
值类型的实例不受垃圾回收器的控制
书中个的理由是
如果所有类型都是引用类型,应用程序的性能将显著下降。
设想假如每次使用一个Int32值时,都进行一次内存分配,性能会受到多么大的影响!
为了提升简单的、常用的类型的性能,CLR提供了名为“值类型”的轻量级类型
从数据结构来说.
栈就是一个后进先出的简单结构.
堆是一种是经过排序的树形结构.
复杂度不一样. 分配的速度自然也不一样.
栈中的内存通过寄存器定位
堆在寄存器中就一个地址,再通过地址去内存里面找理论上也慢一点儿.
不过在现在这么屌的CPU,还有没有区别就不知道了. 或许已经达到可以忽略的地步了.
关于值类型引用类型的分配.
网上很多是这样说的。
值类型 栈
引用类型中的值类型 GC Heap
引用类型 < 85000byte GC Heap
引用类型 > 85000byte Large Object Heap
值类型 和 引用类型的区别
1 public struct ccc 2 { 3 4 } 5 6 public class bbb : aaa 7 { 8 9 } 10 11 var c1 = new ccc(); 12 var c2 = c1; 13 14 var b1 = new bbb(); 15 var b2 = b1;
查看一下内存地址
c1 0x0679EF28
c2 0x0679EF24
b1 0x021E4AAC
b2 0x021E4AAC
引用类型中的值类型
1 var a = new aaa(); 2 var b = a; 3 4 a.c++; 5 6 Console.WriteLine(a.c); 7 Console.WriteLine(b.c);
结果是 1,1
a 和 b 的内存地址都是 0x02314AAC
至于 是在GC Heap 还是 Large Object Heap
这个我没有想到办法验证.
注意事项
本书说了一堆注意事项。 其实稍微多用一点儿 自己就明白了.
唯一要注意的是传递。
1 public struct ccc 2 { 3 4 } 5 6 var a = new ccc(); 7 test(a); 8 9 public static void test(ccc i) 10 { 11 12 Console.WriteLine(i.GetType()); 13 }
看内存地址
a 0x0652E860
i 0x0652E850
因为值类型都在线程栈中. 这样的传递都会复制一次.
特别是面向对象结构一层一层的. 如果 传递的值类型过大. 必定会对性能有一定影响.
当然也有特殊的int,地址是一样的.
装箱和拆箱
其实就是值类型和引用类型的相互转换
值类型转引用类型 装箱.
引用类型转值类型你个 拆箱.
我没有找到测试方法。比如他究竟怎么运作的。 从栈怎么复制过去的。
所以就引用书中的说法。
值类型转引用类型。
- 在堆中分配内存(包括同步索引之类的必须的)
- 复制到堆
- 返回堆中的地址
引用类型转值类型
- 找到堆地址
- 复制到栈
书中特别提了一句
拆箱其实就是获取一个指针的过程,该指针指向包含在一个对象中的原始值类型(数据字段)
我并不是特别理解这一句话。
如果堆中保留了 原始值类型。 可是他依然在堆中。 所以不管怎么样都需要有一次复制操作。
注意事项
1.如果包含了“对已装箱值类型实例的引用”的变量为null,就抛出一个NullReferenceException异常。
2.如果引用指向的对象不是所期待的值类型的一个已装箱实例,就抛出一个InvalidCastException异常。
1 public static void Main() { 2 Int32 x = 5; 3 Object o = x; 4 //对x进行装箱,o引用已装箱的对象 5 Int16 y = (Int16) o; 6 //抛出一个InvalidCastException异常 7 }
Int32 x = 5; Object o = x; Int64 y = (Int64)o;
拆箱必须和原有的类型一致,不管大小都不行.