通过价值传递?

时间:2016-05-17 14:53:41

标签: c# function copy pass-by-value

我遇到了C#函数参数传递的问题。

我想知道如何让C#函数按值接受参数(制作原始对象的副本)。

我认为这是C#处理这些事情的默认方式,但在以下代码中:

using System;
using System.Collections.Generic;
using System.Linq;
class MaximumElement
{
    static void Main(string[] args)
    {
        Stack<int> numbers = new Stack<int>();
        int n = int.Parse(Console.ReadLine());
        for (int i = 0; i < n; i++)
        {
            string input = Console.ReadLine();
            switch (input)
            {
                case "2": numbers.Pop(); break;
                case "3": Console.WriteLine(maxElement(numbers)); break;
                default:
                    string[] argz = input.Split(' ');
                    numbers.Push(int.Parse(argz[1]));
                    break;
            }
        }
    }


    public static int maxElement(Stack<int> stack)
    {
        int max = stack.Peek();
        for (int i = 0; i < stack.Count; i++)
        {
            if (max >= stack.Peek())
            {
                stack.Pop();
            }
            else if (max < stack.Peek())
            {
                max = stack.Pop();
            }
        }
        return max;
    }
}

我的 maxElement() 函数实际上更改了我传递给它的原始堆栈,并且解决它的唯一方法是手动制作我传递给内部函数的堆栈的副本功能。 感谢您提前做出任何回复:)

3 个答案:

答案 0 :(得分:2)

请勿将引用的传递参数与值类型引用类型混合使用。这是一个常见的初学者的错误,你需要清楚地了解这两种东西,尽管在某种程度上是相关的,但它们是完全不同的语言特征。

我可能不会使用精确的术语,因为英语不是我的语言,但我希望我可以理解:

  • 值类型:变量是值本身。当您撰写以下内容时:int i = 1;变量i包含值1
  • 引用类型:变量是指向内存中对象所在位置的引用。这意味着,当您说string s = "Hello"; s不包含"Hello"时,它会包含存储"Hello"的内存地址。

那么当您按值传递参数时会发生什么(默认情况下为C#)。我们有两种可能性:

  • 参数是一种值类型:您获得变量的副本,这意味着 如果你传递i = 1,你也会收到副本 包含1,但两者都是不同的对象。

    在处理 mutable 值类型时很明显,例如System.Drawing.Point

    Point point = new Point(0, 0);
    
    Frob(point);
    var b = point.X == 1 && point.Y == 1; //False, point does not change.
    
    void Frob(Point p) { p.Offset(1, 1); } // p is a copy of point and therefore contains a copy of the value stored in point, not the value itself.
    
  • 参数是一种引用类型:您获得了该变量的副本,这意味着您获得了对内存地址的引用的副本,但该副本指向的对象< em>是相同的。这就是您所处的场景。

    Foo foo = new Foo();
    foo.Blah = 1;
    
    Frob(foo);
    var b = foo.Blah == 2; //True, foo.Blah has been modified.
    
    void Frob(Foo f) { foo.Blah = 2; } //both foo and f point to the same object.
    

    请注意,在这两种情况下,您不能做的是修改引用所指向的。这不起作用:

    string s = "hello";
    foo(s);
    var b = s == "bye"; //false, s still points to the original string
    
    void Foo(string str)
    {
        str = "bye";
    }
    

现在,如果我们通过引用传递会发生什么?好吧,主要区别在于你传递变量本身,而不是副本。这意味着,如果是值类型,则传递原始值,如果是引用类型,则传递原始地址,而不是副本。这允许以下内容:

    //Value type
    Point point = new Point(0, 0);

    Frob(ref point);
    var b = point.X == 1 && point.Y == 1; //True, point and p are the same variable.

    void Frob(ref Point p) { p.Offset(1, 1); }

    //Value or reference type
    string s = "hello";
    foo(ref s);
    var b = s == "bye"; //true

    void Foo(ref string str)
    {
        str = "bye";
    }

希望这能澄清其中的差异。

答案 1 :(得分:1)

你需要制作一个Stack的副本,如果浅拷贝有效,你可以使用Clone()方法。

答案 2 :(得分:1)

有点复杂。来自MSDN(https://msdn.microsoft.com/en-us/library/s6938f28.aspx):

  

引用类型的变量不直接包含其数据;它包含对其数据的引用。按值传递reference-type参数时,可以更改引用指向的数据,例如类成员的值。但是,您无法更改引用本身的值;也就是说,您不能使用相同的引用为新类分配内存并使其在块外保留。为此,请使用ref或out关键字传递参数。为简单起见,以下示例使用ref。

以下是他们提供的代码示例:

static void Change(int[] pArray)
{
    pArray[0] = 888;  // This change affects the original element.
    pArray = new int[5] {-3, -1, -2, -3, -4};   // This change is local.

现在,如果您在参数

上使用ref关键字
static void Change(ref int[] pArray)
{
    pArray[0] = 888;  // This change affects the original element.
    pArray = new int[5] {-3, -1, -2, -3, -4};   // This change also affects the original

所以,考虑到这些事情,你可以......

public static int maxElement(Stack<int> stack)
{
    stack = new Stack<int>(stack); // Now changes will be local

    int max = stack.Peek();
    for (int i = 0; i < stack.Count; i++)
    {
        if (max >= stack.Peek())
        {
            stack.Pop();
        }
        else if (max < stack.Peek())
        {
            max = stack.Pop();
        }
    }
    return max;
}