备忘unity值类型与引用类型

一、值类型和引用类型概念
值(value)类型 :Unity中的vector3、quaternion是structs,它们是值。

引用(feference)类型 :Unity中的transform、gameobject是类类型,所以它们是引用。引用类型是对数据存储位置的引用,引用类型指向的内存区域称为堆。

在决定定义引用类型还是值类型时,关键因素是:如果逻辑上是固定大小不可变的值,就考虑定义成值类型,如果逻辑上是可引用的可变的对象,就定义成引用类型。例如字符串类型,它的大小是根据值的大小而改变的,所以它是引用类型。

所以哪些是值?哪些是引用? 
0. int float bool等是值。 
1. struct是值,class是引用 
2. 数组是引用,即使元素是值类型(int[]是引用) 
3. 枚举(enum)是值 
4. 委托(delegate)是引用 
5. 接口(interface)是引用,但可用值类型实现。

引用类型在堆上,值类型在栈上?

引用类型的实例总是在堆上的没有错,但是方法内部声明的变量和方法参数在栈上。 而且实例变量的值总是存储在实例本身存储的地方。例如如一个类中有个int变量,虽然它是一个值类型,但它在堆上。
引用类型的变量包含了两个存储位置:直接和变量关联的存储位置,以及由变量中存储的值引用的存储位置。
对于直接与变量关联的存储位置和值类型变量关联的存储位置,它们的存储位置没有区别,也就是引用本身会和值类型一样,如果一直变量短时间存在,就在栈的临时存储池中分配。
注意:不要创建消耗内存大于16字节的值类型,因为一个引用的大小就是32字节或64字节,如果复制值类型的代价比作为引用复制时高出四倍,就应该把它设计为引用类型了。

但是值类型也有值类型的好处,值类型的效率更高,而当我们需要从其它作用域修改一个值类型的数据时,我们需要显式传引用;也正因如此,很多数学类都使用结构体类型而非类类型,例如unity中的vector,quaternion等;

在C#中,结构体也有自己的构造函数、常量和方法等,尽管有些许语法区别,但是一个结构体也可以被视为一个轻量级的类;

二、unity中的值类型和引用类型
首先看一组值类型和引用类型的比较:

Vector3:它在unity中被定义为一个结构体,它是一个值类型,如下所示,我们如果修改了pos,它不会和transform.position有任何关系,也就是这个物体的位置并不会改变;

Vector3 pos=transform.position;
pos=Vector3.zero;
Mat:它是一个类类型,也就是引用类型,如下所示,如果修改了mat,那么这个物体的材质的颜色就真的被改变了;

Material mat=GetComponent<MeshRenderer>().material;
mat.color=Color.red;
总结:我们在定义变量需要明白这个变量类型是值类型还是引用类型,如果是引用类型,它到底是指向什么的引用;

三、按值传递引用类型和按引用传递引用类型
我们已经介绍了一个引用类型的变量包括了两部分:它自身所存储的引用以及这个引用所指向的内存;

如果我们使用如下语句,那么引用类型所它自身所存储的引用就是指向当前物体的transform的;第二行代码,我们修改的tr所指向的内存中的变量的内容;但是接下来第三行代码,tr的引用指向了otherObj的transform,这里我们修改的不是tr指向的值,而是它自身所指向的引用;

Transform tr=this.transform;
tr.position=Vector3.One;
tr=otherObj.transform;
在unity中,有时候我们可能会传递一个引用变量,对于引用变量,一共有两种方式,按值传递和按引用传递;

如下是一个案例,我们设计了两个函数,分别是按值传递和按引用传递Transform,然后在函数里修改Transform,我们通过name来观察修改结果;在场景中将target和target2分别用cube1和cube2赋值,首先第一个log会是cube1,那么第二个log呢,我们就要在此了解一下两种传递方式的区别了;

按值传递:我们使用按值传递引用类型,就意味着我们只能通过传递参数来修改这个引用类型所引用的值,函数结束后修改值会被保存到参数中,但是不能修改这个引用类型本身所指向的引用,因为函数结束后这种修改并不会被保存到传入的引用类型变量中;
按引用传递:会修改传入的引用类型参数自身的引用;
所以,调用第一个按值传递时,我们执行的一句用于修改引用自身的代码在函数执行结束后就会失效,t并没有被修改;而第二个按引用传递,t本身就是被修改成了cube2的transform;

    public Transform target;
    public Transform target2;
    void Start()
    {
        Debug.Log(target.name);
 
        ChangeTargetByVal(target);
        Debug.Log(target.name);
 
        ChangeTargetByRef(ref target);
        Debug.Log(target.name);
    }
 
    private void ChangeTargetByVal(Transform t)
    {
        t = target2.transform;
    }
 
    private void ChangeTargetByRef(ref Transform t)
    {
        t=target2.transform;
    }
更多可以参考:C#传递引用类型参数;
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_36383623/article/details/96213731

一、定义和区别


1.值类型包括基础类型(int、float、bool)、枚举类型enum、结构体类型struct。派生自System.ValueType(继承Object)。
引用类型包括类Class、接口Interface、委托delegate、数组ArrayList、字符串String。派生自Object。
扩展:ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。
Vector3、Quaternion是值类型,GameObject、Transform是引用类型。


2.值类型存储的是变量实际的值,引用类型存储的是变量的内存地址,指向托管堆内存。


3.值类型存储在栈上,引用类型存储在托管堆上(地址存在栈上)。
扩展:栈是有序、连续的内存域,由系统自动分配和维护,需要在编译期间预先分配好内存大小。
堆是无序、不连续的内存域,由用户自己控制释放或者触发GC。


4.值类型在赋值时,会生成独立的数据副本,修改新值时,旧的变量不受影响。
引用类型在赋值时,传递的是内存地址,新数据和旧数据指向同一个托管堆数据,修改任意一个值时,另一个也会变化。


5.值类型不可以派生,不可以为空。引用类型可以派生,可以为空。

二、装箱和拆箱


装箱和拆箱的定义:
装箱是值类型转换为引用类型的过程 ;
拆箱是引用类型转换为值类型的过程。
int value = 10;
object obj = value; //装箱
int newValue = (int)obj; //拆箱

装箱和拆箱的内存操作:
装箱操作:
1.生成一个新的引用类型,在托管堆中分配内存。(分配内存)
2.将值类型数据拷贝到分配的内存中。(数据拷贝)
3.返回托管堆对象的地址。

拆箱操作:
1.获取托管堆中值类型部分字段的地址。
2.将引用对象的值拷贝到栈上的新实例中。

什么时候会发生装箱/拆箱:
1.一个包含参数类型为Object的方法,调用该方法时传入了值类型参数,会发生装箱。
2.使用非泛型容器(如ArrayList),将值类型加入容器时,会发生装箱。

如何避免装箱和拆箱的性能开销:
装箱操作会生成新的引用对象并赋值,并且造成GC,会造成较大的开销,因此需要尽量避免:
1.针对上述情况1,使用重载方法避免装箱。
2.针对上述情况2,使用泛型容器避免装箱。
3.对于多次装箱操作,可以考虑提前进行显式装箱,减少装箱次数。



三、值类型和引用类型的嵌套

1.值类型嵌套定义引用类型(struct结构包含class):

值类型嵌套定义引用类型时,栈上将保存该引用类型的地址,而实际的数据则依然保存在托管堆中。

解释

//值类型嵌套定义引用类型的情况 public struct Temp { //结构体字段,注意:结构体中字段不能被初始化 private TestClass testClass; //结构体的构造函数,注意:结构体中不能显式定义无参的构造函数 public Temp(TestClass t) { if(t ==null) thrownewArgumentNullException("t"); testClass = t; testClass.x =10; testClass.y =20; } }

值类型嵌套引用类型



2.引用类型嵌套定义值类型(class包含值类型):


i.类的字段类型是值类型,它将作为引用类型实例的一部分,被分配到托管堆中。
ii.但那些作为局部变量的值类型,则仍然会被分配到线程栈中。

解释

public classTest { // num作为引用类型的一部分被分配到托管堆上 private int num =10; public void Temp() { // d被分配到线程栈上 double d =3.14; } }


引用类型嵌套值类型

四、继承



值类型和引用类型都是有继承的,但是值类型的继承机制是不允许用户自己改变的,而引用类型可以。值类型默认从 System.ValueType 这个类派生,而这个实际上是一个类(即属于引用类型);而引用类型是可以修改继承方式的,比如你有个类 A,你想自定义一个类,然后让 A 继承自这个类,这是允许的。

解释

public class A : Z { } public class Z { } public sealed class Y { } public class B : Y { } // 错误,Y 类型是不能继承的。 public static class X { } public class C : X { } // 错误,X 类型是静态类,只能存储静态方法。 // 所以任何类都没办法继承这个类 //(继承了也没用,毕竟这个类没实例调用的东西)。 public class W : object { } // 错误,W 默认继承自 object,所以你完全不必去写。 public class V : Z { } public class A : V { }


所谓的继承就是为了获取基类型原本的数据信息,拿到自己的类里来用的一种机制,这样你就省下写一大堆相同代码的时间了。
不过,值类型和引用类型都可以实现接口。在值类型使用实现的接口对象的方法时,就可能产生装箱操作,即变为一个引用类型放到堆内存里去。这同样可以验证前文说到的“值类型只是默认放在栈内存里”的这个说法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虾米神探

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值