【Note】C#基础知识(1)
在C#中,数据类型分成了两大类,一是值类型,另外就是引用类型。
而这两者的区别主要是:
- 值类型存储在内存的栈中(线程栈,最大1M),使用完毕后会自动释放掉;引用类型存放在内存的堆中,需要等待垃圾回收机制进行回收。
- 值类型表示实际的数据;引用类型存储对其数据的引用。(PS:引用这其实是一个很模糊的概念,简单的来说,引用是一个小的数据块,CLR根据里面的信息来找到该引用指向的对象)
这里有一个知识点,帮助更好的理解两者的区别,就是自动释放和等待垃圾回收。
这两者需要上升到操作系统的层面,因为值类型存储在栈中,分配的内存比较小,操作系统就能对其进行控制。而引用类型存储在堆中,当新建一个引用类型的时候,就会在堆上给其分配内容,尽管其引用保存在栈上,但其本身创建在堆中,所以当其引用被释放掉的时候,其本身还在内存中,所以释放需要等待垃圾回收。
类型 | 具体内容 |
---|---|
值类型 | 有符号类型:sbyte、short、int、long 无符号类型:byte、ushort、uint、ulong Unicode字符:char 浮点型:float、double 高精度小数:decimal 枚举类型:enum 结构类型:struct |
引用类型 | object、string、class、interface、array、delegate |
补充2个点
- 所有值类型的基类是ValueType。
- DateTime是结构体,所以是值类型。
说到值类型和引用类型就不能不说到装箱和拆箱了。简单的来说:
装箱:值类型转换成引用类型。
拆箱:引用类型转换成值类型。
装箱的过程(本质上和新建一个引用类型的过程一致,唯一的不同点就是有确认的内存大小 - 即值类型外加其他成员变量的内存大小)
- 在堆中分配内存,开辟与值类型相同大小外加其他成员变量的内存空间。
- 将值类型的字段复制到新的堆内存空间中。
- 在栈中添加对该新的内存空间的引用并返回出相应的地址。
拆箱的过程
- 找到堆空间上相应的的变量地址。
- 在栈上开辟相同大小的内存空间存储引用对象中的值。
拆箱只涉及到一次堆空间的查询过程,不需要开辟新的堆空间,所以整体性能比装箱低。但是拆箱比装箱多了一步隐性步骤,就是拆出来的箱子需要被回收,箱子具体指的是存放了值类型字段的引用对象实例。
在C#中,万物皆对象,这个对象是Object。Object有三个虚函数:GetHashCode()、Equals()、ToString()。
现在有一个问题,假设用一个int类型利用ToString()方法是否会发生装箱操作?
答案是不会的。为什么呢?
因为上面说了,所有的值类型继承于ValueType类,而ValueType类Override了Object中的ToString()方法,不用转成object而直接调用Override的ToString()方法创建了一个新的字符串,即已经不在同一个实例下,所以不会发生装箱操作。
1、注意代码中会隐式转换成Object的位置。
例如:
1 | int code = 100; |
这样就code就会等同于赋值给了object o,然后再隐式调用ToString(),就发生了装箱操作。
应该提前手动把code转成string,就能避免装箱。理由在上面已经说到了
1 | int code = 100; |
2、使用泛型