天天看点

C# 值传递和引用传递的理解

首先明确类型:

常用的值类型有:int double char bool decimal struct enum;

常用的引用类型有:string 数组 自定义类 接口 委托;

值类型的对象直接存在于栈上。

引用类型的对象实际存在于堆上,但是“指针”存在于栈上。

   例如 myClass A = new myClass();

  A应该存在于栈上,其内容是一个地址,指向的是new myClass的堆中地址。A应该就是一个代理人,通过A可以修改找到真实的对  象。

然后是传递方式:

对于两种类型的传递,在非out,ref的情况下都是值传递【拷贝】,只不过引用类型传递的是一个地址的拷贝,在函数内对该拷贝的修改实际上是对该拷贝指向的实际内容的修改。值类型是传递的是具体内容的拷贝。

1,按值传递

值类型按值传递,引用类型按值传递的实质的是传递值,参数为值类型时,“值”为实例本身,因此传递的是实例拷贝,不会对原来的实例产生影响;参数为引用类型时,“值”为对象引用,因此传递的是引用地址拷贝,会改变原来对象的引用指向。

string是引用类型,string按值传递的效果与值类型按值传递效果一样,string在这里比较特殊。

调用方法发生参数传递时,方法根据参数类型先在stack创建一个变量,然后将参数的值赋值给该变量。所以,值类型与string类型传递实例不变,引用类型传递地址改变。但如果是按引用传递,则都是传递地址,实例的值都会发生改变。

2,按引用传递

按引用传递之ref和out,不管是值类型还是引用类型,按引用传递必须以ref或者out关键字来修饰,ref要求传递之前的参数必须首先显示初始化,而out不需要。也就是说,使用ref的参数必须是一个实际的对象,而不能指向null;而使用out的参数可以接受指向null的对象,然后在调用方法内部必须完成对象的实体化。

值类型按引用传递时,不会对值类型装箱。

按引用传递,传递的不是参数本身的值,而是参数的地址。如果参数为值类型,则传递的是该值类型的地址;如果参数为引用类型,则传递的是对象引用的地址,引用类型按引用传递结果和按值按引用传递一样。

演示代码:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public struct StructA

{

    public int a;

}

public class ChuandiA

{

    public ChuandiA(int _a)

    {

        a = _a;

    }

    private int a;

    public int A { get { return a; } set { a = value; } }

}

public class ChuandiTest : MonoBehaviour

{

    // Use this for initialization

    void Start()

    {

        Debug.Log("=====修改类的成员====");

        ChuandiA A = new ChuandiA(0); //对象1

        Debug.Log(A.A);

        Debug.Log("=======================");

        ChangeA(A);

        Debug.Log(A.A);

        Debug.Log("=====Ref修改类的成员====");

        ChuandiA B = A; //B拷贝了A的内容,B和A都指向了对象1。

        Debug.Log(A.A);

        Debug.Log(B.A);

        Debug.Log("=======================");

        ChangeA(ref A);

        Debug.Log(A.A); //B仍然指向对象1,A已经指向了新的对象。

        Debug.Log(B.A); //B仍然

        Debug.Log("=====修改结构体的成员===");

        StructA sA = new StructA();

        StructA sA2 = sA;

        sA.a = 33;       

        Debug.Log(sA.a);

        ChangeA2(sA);

        Debug.Log("=======================");

        Debug.Log(sA.a);

        Debug.Log("=======使用ref修改结构体的成员========");

        sA.a = 44;

        Debug.Log(sA.a);

        ChangeA2(ref sA);

        Debug.Log("=======================");

        Debug.Log(sA.a);

    }

    public void ChangeA(ChuandiA _ChuandiA)

    {

        //此时的_ChuandiA是传入参数的拷贝,由于传入的参数是引用类型,所以_ChuandiA的内容是一个地址,指向传入参数。

        //首先通过地址修改了【传入对象】的内容;

        _ChuandiA.A = 3;

        //然后指向了新的地址,并不会影响传入对象的内容;

        _ChuandiA = new ChuandiA(99);

        //此时的_ChuandiA 指向新的ChuandiA,对旧的对象[sA]无影响。

    }

    public void ChangeA(ref ChuandiA _ChuandiA)

    {

        //_ChuandiA就是传入参数,原来传入参数是一个指针,指向真实的引用类型对象。

        _ChuandiA.A = 444;

        //修改了_ChuandiA的指向,也就是修改了传入参数的内容【地址】.

        _ChuandiA = new ChuandiA(99);

        //传入参数原来指向的对象仍然是444,不受影响。

    }

    public void ChangeA2(StructA _ChuandiA)

    {

        //值类型的值传递是拷贝。此时的_ChuandiA是对传入参数的一个拷贝。

        _ChuandiA.a = 3;

    }

    public void ChangeA2(ref StructA _ChuandiA)

    {

        //值类型的引用传递不是拷贝,此时的_ChuandiA就是传入参数。

        _ChuandiA.a = 3;

    }

}