目录
1.前言
学习C#也快两年了,之前接触过C,C++,都不深入,但是知道基础,所以在C#学习并没有在基础上用过多时间,后来跟项目,遇到问题,现学现卖,快要找工作了,觉得基础的东西还是得再学习下,今天就先看看C#值类型和引用类型的区别。
2.概念
c#中有两种基本类型,它们分别是值类型和引用类型
值类型
变量直接存储在内存中,byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。所有的值类型都是隐式密封的(sealed)(无法派生出新的值类型),目的是防止其他任何类型从值类型进行派生。 值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
C#的所有值类型均隐式派生自System.ValueType:
结构体:struct(直接派生于System.ValueType);
数值类型:
- 整型:sbyte(System.SByte的别名),short(System.Int16),int(System.Int32),long(System.Int64),byte(System.Byte),ushort(System.UInt16),uint(System.UInt32),ulong(System.UInt64),char(System.Char);
- 浮点型:float(System.Single),double(System.Double);
- decimal(System.Decimal),用于财务计算的高精度。
- bool型:bool(System.Boolean的别名);
- struct(派生于System.ValueType)。
- enum(派生于System.Enum);
- 可空类型(派生于System.Nullable<T>泛型结构体,T?实际上是System.Nullable<T>的别名)。
值得注意的是,引 用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。
可以用Type.IsValueType属性来判断一个类型是否为值类型:
TestType testType = new TestType ();
if (testTypetype.GetType().IsValueType)
{
Console.WriteLine("{0} is value type.", testType.ToString());
}
引用类型
引用类型的变量持有的是数据的引用,数据存储在数据堆中,string 和 class统称为引用类型。当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
C#有以下一些引用类型:
- 数组(派生于System.Array)
- 用户用定义的以下类型:
- 类:class(派生于System.Object);
- 接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。Anders在《C# Programming Language》中说,接口只是表示一种约定[contract]);
- 委托:delegate(派生于System.Delegate)。
- object(System.Object的别名);
- 字符串:string(System.String的别名)。
string的探索
明明string赋值都是值类型,咋就是引用类型呢?
.net框架程序设计(修订版)中有这样一段描述:
string 类型直接继承Object,这使得它成为一个引用类型,也就是说线程的堆栈上不会驻留任何字符串。
名称 |
CTS类型 |
说明 |
string |
System.String |
Unicode字符串 |
string s1 = "aaaa";
string s2 = s1;
Console.WriteLine("s1:" + s1);
Console.WriteLine("s2:" + s2);
s1 = "bbbb";
Console.WriteLine("s1:" + s1);
Console.WriteLine("s2:" + s2);
Console.ReadKey();
在String字符串上,最开始S1地址指向aaaa,由于S2=S1,所以S2地址也同样指向aaaa。如图所示
当给S1再次赋值bbbb时,堆中就会开辟出数据bbbb,而且aaaa数据没有消失,没有被覆盖。S1地址就会指向堆中bbbb
S2地址还是指向aaaa。如图所示
上面代码值类型的赋值,但string是一个引用类型。String被分配在堆上,而不是栈上。因此,当把一个字符串变量赋给另一个字符串时,会得到对内存中同一个字符串的两个引用。但是,string与引用类型在常见的操作上有一些区别。例如字符串是不可改变的。修改其中一个字符串,就会创建一个全新的string对象,而另一个字符串不会发生任何变化。
3.通用系统类型(CTS)
C#的基本数据类型都以与平台无关的方式来定义。C#的预定义类型并没有内置于语言中,而是内置于.NET Framework中。所有面向.NET的语言都最终被编译为IL。
例如,在C#中声明一个int变量时,声明的实际上是CTS中System.Int32的一个实例。这具有重要的意义:
确保IL上的强制类型安全;
实现了不同.NET语言的互操作性;
所有的数据类型都是对象。它们可以有方法,属性,等。例如:ToString()。
4.值类型和引用类型在内存中的部署
栈和堆的了解
栈
栈只能在一端对数据进行操作,也就是栈顶端进行操作。’
栈也是一种内存自我管理的结构,压栈自动分配内存,出栈自动清空所占内存。
另外值得注意的两点:
1.栈中的内存不能动态请求,只能为大小确定的数据分配内存,灵活性不高,但是栈的执行效率很高。
2.栈的可用空间并不大,所以我们在操作分配到栈上的数据时要注意数据的大小带来的影响。
堆
堆与栈有所区别,堆在C#中用于存储实实例对象,能存储大量数据,而且堆能够动态分配存储空间。
相比栈只能在一端操作,堆中的数据可以随意存取。
但堆的结构使得堆的执行效率不如栈高,而且不能自动回收使用过的对象。对于堆中的内存回收,C++程序员需要进行手动回收,这也是C++编程值得注意的一点,否则很容易造成内存溢出。而对于.NET程序员,平台提供了垃圾回收(GC)机制,可以自动回收堆中过期的对象。
值类型和引用类型在栈和堆中的分配
这儿有两个原则:
(1)创建引用类型时,runtime会为其分配两个空间,一块空间分配在堆上,存储引用类型本身的数据,另一个块空间分配在栈上,存储对堆上数据的引用(实际上存储的堆上的内存地址,也就是指针)。
(2)创建值类型时, runtime会为其分配一个空间,这个空间分配在变量创建的地方,如:
如果值类型是在方法内部创建,则跟随方法入栈,分配到栈上存储。
如果值类型是引用类型的成员变量,则跟随引用类型,存储在堆上。
更加详细的介绍参考:https://blog.csdn.net/qiaoquan3/article/details/51202926
5.值类型与引用类型适用场合
值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。
在C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:
SomeType[] oneTypes = new SomeType[100];
如 果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后 数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主 要是存储数据,值类型比较合适。
一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:
该类型的主要职责用于数据存储。
该类型的共有接口完全由一些数据成员存取属性定义。
该类型永远不可能有子类。
该类型不具有多态行为。