Unity C# 数据类型和内存分配的简单总结

C# 是一种强类型语言,每个变量都必须指定数据类型。

C# 的数据类型分为值类型(Value types),引用类型(Reference types),指针类型(Pointer types)。

值类型包括整型、浮点型、字符型、布尔型、枚举型等;
引用类型包括类、接口、数组、委托、字符串等。

数据存储——栈和堆

栈和堆中主要放置了四种类型的数据:值类型(Value Type),引用类型(Reference Type),指针(Pointer),指令(Instruction)。

栈负责保存我们的代码执行(或调用)路径,而堆则负责保存对象(或者说数据,接下来将谈到很多关于堆的问题)的路径。

可以将栈想象成一堆从顶向下堆叠的盒子。当每调用一次方法时,我们将应用程序中所要发生的事情记录在栈顶的一个盒子中,而我们每次只能够使用栈顶的那个盒子。当我们栈顶的盒子被使用完之后,或者说方法执行完毕之后,我们将抛开这个盒子然后继续使用栈顶上的新盒子。

堆的工作原理比较相似,但大多数时候堆用作保存信息而非保存执行路径,因此堆能够在任意时间被访问。与栈相比堆没有任何访问限制,堆就像床上的旧衣服,我们并没有花时间去整理,那是因为可以随时找到一件我们需要的衣服,而栈就像储物柜里堆叠的鞋盒,我们只能从最顶层的盒子开始取,直到发现那只合适的。

如果需得到一个类型或一个变量在特定平台上的准确尺寸,可以使用 sizeof 方法。表达式 sizeof(type) 产生以字节为单位存储对象或类型的存储尺寸。

1.值类型(Value types)

C#的所有值类型均隐式派生自System.ValueType;
判断是否为值类型Type.IsValueType

——内置值类型——

值类型包括整型、浮点型、字符型、布尔型、枚举型等。

值类型变量可以直接分配给一个值。从内存存储空间的角度而言,值类型的值是存放到栈中的,每次存取值都会在该内存中操作。
在这里插入图片描述

整型(包括单个字符char)

整型就是存储整数的类型,按照存储值的范围不同,C# 将整型分成了 byte 类型、short 类型、int 类型、long 类型等,并分别定义了有符号数和无符号数。

有符号数可以表示负数,无符号数仅能表示正数

short、int 和 long 类型所对应的无符号数类型都是在其类型名称前面加上了 u 字符。

byte 类型比较特殊,它存储一个无符号数,其对应的有符号 数则是 sbyte。

字符型只能存放一个字符,它占用两个字节,能存放一个汉字。
字符型用 char 关键字表示,存放到 char 类型的字符需要使用单引号括起来,例如 ‘a’、‘中’ 等。

浮点型

浮点型是指小数类型,浮点型在 C# 语言中有float,double,decimal。float为单精度浮点型,double为双精度浮点型,

数值中除了整数外,还包含小数部分的是"floating point types"(浮点数据类型)。

高精度类型

decimal为高精度浮点型。

如果处理的数值需要精确度高时,可以使用decimal(高精度浮点数),与float(浮点数)和double(双精度浮点数)相比更加精确。

布尔型

布尔类型使用 bool 来声明,它只有两个值,即 true 和 false。

当某个值只有两种状态时可以将其声明为布尔类型,例如,是否同意协议、是否购买商品等。

布尔类型的值也被经常用到条件判断的语句中,例如,判断某个值是否为偶数、判断某个日期 是否是工作日等。

——用户定义值类型——

除了C#预定义的基本类型外,还有两种自定义的值类型,分别是结构和枚举.

结构体类型(派生于System.ValueType)

 public struct book
{
    
    
   public string bookname;
   public string bookno;
   public int bookwrite;
}

使用结构也很简单,代码如下:

  Book book;
  book.bookname = "C#入门"
  book.bookno   = "www.Microsoft.com"
  book.bookwrite= "Microsoft"

可以使用new关键字来初始化结构,结构也可以使用构造函数。

枚举类型(派生于System.Enum)

枚举,枚举是用户定义的整数类型.枚举的意义在于它更好地实现了代码的可读性和数据的复用性,设想一下,在系统中定义红颜色,使用color.red比较容易理解还是用255255比较容易理解.使用enum关键字定义枚举,示例如下:

public enum booktype
{
    
    
   language =0,
   internet =1,
   novel    =2
}

使用也很简单,代码如下:

   booktype booktype  =booktype.language;

2.引用类型(Reference types)

数组(派生于System.Array)
用户定义的类型:
· 类:class(派生于System.Object);
· 接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。
· 委托:delegate(派生于System.Delegate)。
object(System.Object的别名);
字符串:string(System.String的别名)。

引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。

引用类型创建时,首先会在栈中创建一个引用变量,然后在堆中创建对象本身,再把这个对象所在内存的首地址赋给引用变量。

换句话说,引用变量保存的是一个内存位置。使用多个变量时,引用类型可以指向同一个内存位置。如果内存位置的数据被一个变量改变了,其他变量会自动反映这种值的变化。

数组(派生于System.Array)

用户可以自定义的引用类型

类class 委托delegate,接口interface

对象(Object)类型

对象(Object)类型 是 C# 通用类型系统(Common Type System - CTS)中所有数据类型的终极基类。Object 是 System.Object 类的别名。所以对象(Object)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。

当一个值类型转换为对象类型时,则被称为 装箱;另一方面,当一个对象类型转换为值类型时,则被称为 拆箱。

object obj;
obj = 100; // 这是装箱

字符串(String)类型

字符串(String)类型 允许您给变量分配任何字符串值。字符串(String)类型是 System.String 类的别名。它是从对象(Object)类型派生的。字符串(String)类型的值可以通过两种形式进行分配:引号和 @引号。

3.指针类型(Pointer types)

引用通常就是一个指针。我们不会显示的使用指针,它们由公共语言运行时(CLR)来管理。

指针(或引用)是不同于引用类型的,是因为当我们说某个事物是一个引用类型时就意味着我们是通过指针来访问它的。指针是一块内存空间,而它指向另一个内存空间。就像栈和堆一样,指针也同样要占用内存空间,但它的值是一个内存地址或者为空。

类型嵌套时的内存分配

更容易让人困惑的是引用类型包含值类型,以及值类型包含引用类型的情况。
下面有几条准则,请务必结合实例来理解!

  1. 引用类型总是放在堆中。

  2. 值类型和指针总是放在它们被声明的地方。(这条稍微复杂点,需要知道栈是如何工作的,然后才能断定是在哪儿被声明的。)

  3. 当值类型数据在方法体(函数)中被声明时,它们都是被放置在栈上的。

  4. 值类型数据有时也被放置在堆上。记住这条规则–值类型总是放在它们被声明的地方。如果一个值类型数据在方法体外被声明,且存在于一个引用类型中,那么它将被堆中的引用类型所取代。

——实例——

实例的原文链接

数组实例

考虑数组:

int[] reference = new int[100];

根据定义,数组都是引用类型,所以int数组当然是引用类型(即reference.GetType().IsValueType为false)。

而int数组的元素都是int,根据定义,int是值类型(即reference[i].GetType().IsValueType为true)。那么引用类型数组中的值类型元素究竟位于栈还是堆?

如果用WinDbg去看reference[i]在内存中的具体位置,就会发现它们并不在栈上,而是在托管堆上。

实际上,对于数组:

TestType[] testTypes = new TestType[100];

如果TestType是值类型,则会一次在托管堆上为100个值类型的元素分配存储空间,并自动初始化这100个元素,将这100个元素存储到这块内存里。

如果TestType是引用类型,则会先在托管堆为testTypes分配一次空间,并且这时不会自动初始化任何元素(即testTypes[i]均为null)。等到以后有代码初始化某个元素的时候,这个引用类型元素的存储空间才会被分配在托管堆上。

类实例

//类
public class  ReferenceTypeClass
{
    
    
    private int  _valueTypeField;
    public  ReferenceTypeClass()
     {
    
    
         _valueTypeField = 0 ;
     }
    public void  Method()
     {
    
    
        int valueTypeLocalVariable = 0 ;
     }
}
ReferenceTypeClass referenceTypeClassInstance = new ReferenceTypeClass();
//Where is _valueTypeField?

referenceTypeClassInstance.Method();
//Where is valueTypeLocalVariable?

//结构体
public struct  ValueTypeStruct
{
    
    
    private object  _referenceTypeField;
    public void  Method()
     {
    
    
         _referenceTypeField = new object ();
        object referenceTypeLocalVariable = new object ();
     }
}
ValueTypeStruct valueTypeStructInstance = new  ValueTypeStruct();
valueTypeStructInstance.Method();
//Where is _referenceTypeField?And where is referenceTypeLocalVariable?

单看valueTypeStructInstance,这是一个结构体实例,感觉似乎是整块扔到栈上的。但是字段_referenceTypeField是引用类型,局部变量referenceTypeLocalVarible也是引用类型。

referenceTypeClassInstance也有同样的问题,referenceTypeClassInstance本身是引用类型,似乎应该整块部署在托管堆上。但字段_valueTypeField是值类型,局部变量valueTypeLocalVariable也是值类型,它们究竟是在栈上还是在托管堆上?

规律是:

引用类型部署在托管堆上;
值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。
我们来分析一下上面的代码。对于引用类型实例,即referenceTypeClassInstance:

从上下文看,referenceTypeClassInstance是一个局部变量,所以部署在托管堆上,并被栈上的一个引用所持有;
值类型字段_valueTypeField属于引用类型实例referenceTypeClassInstance的一部分,所以跟随引用类型实例referenceTypeClassInstance部署在托管堆上(有点类似于数组的情形);
valueTypeLocalVariable是值类型局部变量,所以部署在栈上。
而对于值类型实例,即valueTypeStruct:

根据上下文,值类型实例valueTypeStructInstance本身是一个局部变量而不是字段,所以位于栈上;
其引用类型字段_referenceTypeField不存在跟随的问题,必然部署在托管堆上,并被一个引用所持有(该引用是valueTypeStruct的一部分,位于栈);
其引用类型局部变量referenceTypeLocalVariable显然部署在托管堆上,并被一个位于栈的引用所持有。
所以,简单地说“值类型存储在栈上,引用类型存储在托管堆上”是不对的。必须具体情况具体分析。

猜你喜欢

转载自blog.csdn.net/weixin_44394801/article/details/119791004
今日推荐