按值传递和按引用传递数组

39

这些是我正在阅读的C#书籍中的示例,我有点难以理解这个示例的实际作用,希望能够解释一下,以便更好地理解这里发生了什么。

        //creates and initialzes firstArray
        int[] firstArray = { 1, 2, 3 };

        //Copy the reference in variable firstArray and assign it to firstarraycopy
        int[] firstArrayCopy = firstArray;

        Console.WriteLine("Test passing firstArray reference by value");


        Console.Write("\nContents of firstArray " +
            "Before calling FirstDouble:\n\t");

        //display contents of firstArray with forloop using counter
        for (int i = 0; i < firstArray.Length; i++)
            Console.Write("{0} ", firstArray[i]);

        //pass variable firstArray by value to FirstDouble
        FirstDouble(firstArray);

        Console.Write("\n\nContents of firstArray after " +
            "calling FirstDouble\n\t");

        //display contents of firstArray
        for (int i = 0; i < firstArray.Length; i++)
            Console.Write("{0} ", firstArray[i]); 

        // test whether reference was changed by FirstDouble
        if (firstArray == firstArrayCopy)
            Console.WriteLine(
                "\n\nThe references refer to the same array");
        else
            Console.WriteLine(
                "\n\nThe references refer to different arrays");

       //method firstdouble with a parameter array
       public static void FirstDouble(int[] array)
    {
        //double each elements value
        for (int i = 0; i < array.Length; i++)
            array[i] *= 2;

        //create new object and assign its reference to array
        array = new int[] { 11, 12, 13 };

基本上,我想知道的是,如果数组是按值传递的,那么原始调用者在方法中不会被修改(从我的理解来看)。因此,在方法FirstDouble的末尾,他们尝试将局部变量数组分配给一组新元素,但失败了,当显示原始调用者的新值为2、4、6时。
现在我的困惑是,如果按值传递,方法FirstDouble中的for循环如何修改原始调用者firstArray为2、4、6,而不是保留原始值1、2、3。
谢谢!

可能是值类型和引用类型问题的重复。 - Alexei Levenkov
@AlexeiLevenkov,由于其他地方已经很好地涵盖了这个问题,我不想因为这个问题而关闭。 - user166390
4个回答

71
了解这一点的关键在于知道值类型和引用类型之间的区别。
例如,考虑一个典型的值类型,int
int a = 1;
int b = a;
a++;

执行此代码后,a 的值为2,b 的值为1。因为int是值类型,b = a会复制a的值。现在考虑一个类:
MyClass a = new MyClass();
a.MyProperty = 1;
MyClass b = a;
a.MyProperty = 2;

因为类是引用类型,所以b = a只是分配引用而不是值。因此,ba都指向同一个对象。因此,在执行a.MyProperty = 2之后,b.MyProperty == 2,因为ab指向同一个对象。
考虑到您问题中的代码,数组是一种引用类型,因此对于这个函数:
public static void FirstDouble(int[] array)

变量array实际上是一个引用,因为int[]是一个引用类型。所以array是一个通过值传递的引用

因此,在函数内部对array进行的修改实际上被应用于array所引用的int[]对象。因此,这些修改对所有引用指向同一对象的引用都可见。这包括调用者持有的引用。

现在,如果我们看一下这个函数的实现:

public static void FirstDouble(int[] array)
{
    //double each elements value
    for (int i = 0; i < array.Length; i++)
        array[i] *= 2;

    //create new object and assign its reference to array
    array = new int[] { 11, 12, 13 };
}

有一个进一步的复杂情况。这个for循环仅仅是将传递给函数的int[]数组中的每个元素都翻倍。这是调用者看到的修改。第二部分是将新的int[]对象赋值给局部变量array。这对调用者不可见,因为它只是改变了引用array的目标。而且由于引用array是按值传递的,所以调用者看不到这个新对象。
如果这个函数是这样声明的:
public static void FirstDouble(ref int[] array)

如果使用引用传递了参考array,那么调用者在函数返回时会看到新创建的对象{ 11, 12, 13 }

1
@pst,也许有点令人困惑,但并不是错误的。引用类型被称为引用类型是有原因的。 - svick
1
@svick 这个措辞是错误的,因为它很容易让人产生困惑。说“x是一个引用”并不等同于说“typeof(x)是一个引用类型”。变量本身并不是一个引用。我们需要区分变量的语义和值的语义(变量用来“引用对象”的值)。 - user166390
2
@David 在反复阅读了您的回复之后,我终于明白您在说什么。那真是令人困惑,但感谢您详细的解释,它确实有所帮助。 - Tim
很棒的解释!! - Antiohia
@DavidHeffernan 这是你的博客吗?https://newbedev.com/passing-arrays-by-value-and-by-reference :) - Avantha Siriwardana
显示剩余12条评论

20

这些术语使用起来真是令人困惑!

为了澄清,

  1. 对于一个 foo(int[] myArray) 方法,"按值传递引用(对象)" 其实意味着 "传递对象地址(引用)的一份拷贝"。这个 '拷贝' 的值,也就是 myArray,最初是原始对象的地址(引用),也就是指向原始对象的指针。因此,任何对 myArray 所指向内容的更改都会影响原始对象的内容。

    但是,由于 myArray 本身的 '值' 是一份拷贝,所以对这个 '值' 的更改不会影响原始对象或其内容。

  2. 对于一个 foo(ref int[] refArray) 方法,"按引用传递引用(对象)" 意味着 "直接传递对象地址(引用)本身(而不是拷贝)"。这意味着 refArray 实际上是对象本身的原始地址,而不是拷贝。因此,对 refArray 的 '值' 或所指向内容的更改都是直接对原始对象本身的更改。


8

除非你明确看到refout,否则所有方法参数都是按值传递的。

数组是引用类型。这意味着你通过值传递一个引用。

只有在将新数组分配给它时才会更改引用本身,这就是为什么这些赋值没有反映在调用方的原因。当你对对象(这里是数组)进行解引用并修改底层值时,你没有改变变量,只改变了它所指向的内容。尽管变量(即它所指向的内容)保持不变,但调用者也能“看到”这个变化。


0

对于那些熟悉 .net 开源项目的人,实现逻辑的想法;

//Sample Code, Illustration;
Method1(params dynamic[] var1) {
  var1[0]=new dynamic[3] { 1,2,3 }
}

var1未指定或无法引用? 一个使用场景是...

//Sample Code, Illustration;
dynamic[] test = new dynamic[];
Method1( ref test,x,x,x,x  );
System.Windows.MessageBox.Show( test[2].ToString() );

仅在不是特定参数时才指示ref; 以及对数组项的引用;

//result is IndexOutOfBounds;

这只是一个示例,可以通过返回数组并像这样使用来完成:

test = Method1( test,... );

改为:

Method1( ref test,x,x,..., ref test[x], ref test2, ... );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接