1

以下のコードでは、デバッガーでコードをステップ実行しながら変数を調べるCharsと、char 配列のサイズは、最後の反復の戻り行の前では 0 ですが、戻り行の後では 1 であり、元の値に戻り続けます。サイズ。

なぜこうなった?事前に助けてくれてありがとう。

static void Main(string[] args)
{
    string str = "Hello";
    PrintReverse(str.ToArray()); // prints "olleH"
    Console.Read();
}

static void PrintReverse(char[] Chars)
{
    Console.Write(Chars[Chars.Length - 1]);
    Array.Resize(ref Chars, Chars.Length - 1);
    if (Chars.Length == 0) return;
    PrintReverse(Chars);
}
4

3 に答える 3

2

ここには 2 つの問題があります。

まず、'before/after return' の問題は、2 つの異なる実行フレームが表示されることを意味します。つまり、デバッガーでは、PrintReverseそれぞれが独自のコンテキスト内に存在するため、スタック トレースに多数の s が重なり合って表示されます。同時に、それ自身の状態。それはほとんど (実際にはそうではありませんが) そのメソッドの「インスタンス」に似ており、2 つの異なるメソッドが表示されています。

第 2 に、それぞれが独自の状態を持っているため、それぞれのローカル変数 (重要なことにパラメーターを含む) も複製されます。最初は同じヒープ オブジェクト (最初の Char 配列) を指していますが、それらはすべて異なる変数です。

さて、このコードを見てください:

char[] test1 = new char[] { '1', '2', '3' }, test2 = test1;
Array.Resize(ref test2, 2);
MessageBox.Show(new string(test1) + " - " + new string(test2)); // result: 123 - 12

これを実行すると、最初は変数が同じオブジェクトを参照していましたが、Array.Resizeによって新しいオブジェクトが作成され、渡された変数の参照が新しいオブジェクトを指すように変更されていることがわかります。最初の変数の参照は、古い (不変の) オブジェクトを指したままです。

これは、Chars パラメータのみを使用して、あなたの場合に起こっていることです。各メソッドでは、Array.Resize() を使用して別の場所を指すように Chars を再割り当てしますが、元の変数は古い場所を参照したままです。

于 2012-06-13T15:48:44.583 に答える
2

refパラメータ宣言に追加してみてください。これで、期待どおりに動作するはずです。

refへの呼び出しがなければ、Array.Resizeローカル配列参照のみを変更でき、から渡された参照は変更できませんMain

    static void Main(string[] args)
    {
        string str = "Hello";
        var array = str.ToArray();
        PrintReverse(ref array);
        Console.Read();
        Debug.Assert(array.Length == 0);
    }
    static void PrintReverse(ref char[] Chars)
    {
        Console.Write(Chars[Chars.Length - 1]);
        Array.Resize(ref Chars, Chars.Length - 1);
        if (Chars.Length == 0) return;
        PrintReverse(ref Chars);
    }

編集:

私は ref が浅いクローンを引き起こすと誤解していました。これがその証拠です:

    static void Main(string[] args)
    {
        var array = new[] { new object() };
        TestRef(ref array, array);
    }

    static void TestRef(ref object[] arrayByRef, object[] arrayByValue)
    {
        Debug.Assert(ReferenceEquals(arrayByRef, arrayByValue)); //no difference whether passed by ref or value, if there was a shallow clone happening, this would fail
        Array.Resize(ref arrayByRef, 2);
        Debug.Assert(!ReferenceEquals(arrayByRef, arrayByValue)); //only now do they differ
    }
于 2012-06-13T15:42:15.293 に答える
1

実行の連鎖について考えてください。0になるまで配列を縮小するメソッドを再帰的に呼び出してから、呼び出し元(同じメソッド)に戻ると、呼び出しスタックをトラバースして元のサイズに戻ることがわかります。

再帰呼び出しはメソッドの最後の呼び出しであるため、この結果として余分なロジックは発生しませんが、デバッガーが各呼び出しを終了し、その前の呼び出しよりも配列サイズが1大きいことがわかります。

ただし、代わりに参照によって配列を渡す場合、Array.Resizeを連続して呼び出すたびに新しい配列が作成され、すべての参照が新しい配列に更新されるため、配列サイズは呼び出しスタックに到達したときにサイズ0のままになります。その呼び出しに対してローカルな参照だけではなく、配列。(参照を渡さない場合と同様に、参照のコピーのみが更新され、その前の呼び出しのコピーは更新されません)。

これは、Array.Resizeが新しい配列を作成し、古い配列ではなく新しい配列を指すように参照を更新し、参照を渡さないことで、実際の参照ではなく元の配列に参照のコピーを送信するためです。したがって、Array.Resizeを呼び出しても、古い参照は更新されません。

static void PrintReverse(ref char[] Chars)
{
    Console.Write(Chars[Chars.Length - 1]);
    Array.Resize(ref Chars, Chars.Length - 1);
    if (Chars.Length == 0) return;
    PrintReverse(ref Chars);
}

私を訂正してくれたGrooに感謝します、うまくいけば今回はそれが正しいです

于 2012-06-13T15:37:40.053 に答える