參數傳遞 以及 Ref和Out的區別

 值類型和引用類型的區別:

值類型(Value Type),值類型實例通常分配在線程的堆棧(stack)上,並且不包含任何指向實例數據的指針,因爲變量本身就包含了其實例數據

引用類型(Reference Type),引用類型實例分配在託管堆(managed heap)上變量存儲對值的內存地址的引用。

 

using System;
public class Test
{
     static void Main()
     {
         //定義值類型和引用類型,並完成初始化
         MyStruct myStruct new MyStruct();
         MyClass myClass new MyClass();
        
         //定義另一個值類型和引用類型,
         //以便了解其內存區別
         MyStruct myStruct2 new MyStruct();
         myStruct2 myStruct;
        
         MyClass myClass2 new MyClass();
         myClass2 myClass;        
     }
}

C#參數傳遞詳解

              內存概況

 

根據參數類型和傳遞方式不同,有以下4中不同的情況:
  • 值類型參數的按值傳遞
  • 引用類型參數的按值傳遞
  • 值類型參數的按引用傳遞
  • 引用類型參數的按引用傳遞

1.值類型的參數的按值傳遞

值類型實例傳遞的是該值類型實例的一個拷貝,因此被調用方法操作的是屬於自己本身的實例拷貝,因此不影響原來調用方法中的實例值。

using System;
namespace Anytao.net.My_Must_net
{
     class Args
     {
         public static void Main()
         {
             int 10;
             Add(a);
             Console.WriteLine(a); //輸出結果爲10
         }

         private static void Add(int i)
         {
            10;
             Console.WriteLine(i); 
         }
     }
}

2.引用類型參數的按值傳遞

當傳遞的參數爲引用類型時,傳遞和操作的是指向對象的引用,這意味着方法操作可以改變原來的對象,但是值得思考的是該引用或者說指針本身還是按值傳遞的。

using System;

namespace Anytao.net.My_Must_net
{
     class Args
     {
         public static void Main()
         {
             ArgsByRef abf new ArgsByRef();
             AddRef(abf);
             Console.WriteLine(abf.i); //輸出結果爲20
         }

         private static void AddRef(ArgsByRef abf)
         {
             abf.i 20;
             Console.WriteLine(abf.i);
         }

     }

     class ArgsByRef
     {
         public int 10;
     }
}

按值傳遞的實質的是傳遞值,不同的是這個值在值類型和引用類型的表現是不同的:參數爲值類型時,“值”爲實例本身,因此傳遞的是實例拷貝,不會對原來的實例產生影響;參數爲引用類型時,“值”爲對象引用,因此傳遞的是引用地址拷貝,會改變原來對象的引用指向,這是二者在統一概念上的表現區別,理解了本質也就抓住了根源。

3.string類型作爲引用類型的特殊性

using System;
namespace Anytao.net.My_Must_net
{
     class Test    

     {

         private int i = 10;


         static void Main()
         {
             string str "Old String";

             Test test=new Test();
             ChangeStr(str);

             ChangeObj(test);
             Console.WriteLine(str); //輸出結果爲Old String

             Console.WriteLine(test.i); //輸出結果爲10
         }
         static void ChangeObj(Test t)

         {

             t=new Test(); //這裏將創建一個新的對象,所以改變它不會對原對象有影響

             t.i=20;

         }
         static void ChangeStr(string aStr)
         {
             aStr "Changing String";
             Console.WriteLine(aStr);
         }
     }
}

下面對上述示例的執行過程簡要分析一下:首先,string str = "Old String"產生了一個新的string對象,然後執行ChangeStr(aStr),也就是進行引用類型參數的按值傳遞,我們強調說這裏傳遞的是引用類型的引用值,也就是地址指針;然後調用ChangeStr方法,過程aStr = "Changing String"完成了以下的操作,先在新的一個地址生成一個string對象,該新對象的值爲"Changing String",引用地址爲0x06賦給參數aStr,因此會改變aStr的指向,但是並沒有改變原來方法外str的引用地址,

 

因此執行結果就可想而知,我們從分析過程就可以發現string作爲引用類型,在按值傳遞過程中和其他引用類型是一樣的。如果需要完成ChangeStr()調用後,改變原來str的值,就必須使用ref或者out修飾符,按照按引用傳遞的方式來進行就可以了,屆時aStr = "Changing String"改變的是str的引用,也就改變了str的指向。

4.按引用傳遞之ref和out

不管是值類型還是引用類型,按引用傳遞必須以ref或者out關鍵字來修飾,其規則是:

方法定義和方法調用必須同時顯示的使用ref或者out,否則將導致編譯錯誤;

using System;
namespace Anytao.net.My_Must_net._11_Args
{
     class TestRefAndOut
     {
         static void ShowInfo(string str)
         {
             Console.WriteLine(str);
         }
         static void ShowInfo(ref string str)
         {
             Console.WriteLine(str);
         }
     }
}

當然,按引用傳遞時,不管參數是值類型還是引用類型,按引用傳遞時,傳遞的是參數的地址,也就是實例的指針。ref和out關鍵字將告訴編譯器,方法傳遞的是參數地址,而不是參數本身。

如果參數是引用類型,則按引用傳遞時,傳遞的是引用的引用而不是引用本身,類似於指針的指針概念。

  

  

Ref和Out的區別:

ref和out的區別在C# 中,既可以通過值也可以通過引用傳遞參數。通過引用傳遞參數允許函數成員更改參數的值,並保持該更改。若要通過引用傳遞參數, 可使用ref或out關鍵字。ref和out這兩個關鍵字都能夠提供相似的功效,其作用也很像C中的指針變量。它們的區別是:

1、使用ref型參數時,傳入的參數必須先被初始化。對out而言,必須在方法中對其完成初始化。

2、使用ref和out時,在方法的參數和執行方法時,都要加Ref或Out關鍵字。以滿足匹配。

3、out適合用在需要retrun多個返回值的地方,而ref則用在需要被調用的方法修改調用者的引用的時候。

注:在C#中,方法的參數傳遞有四種類型:傳值(by value),傳址(by reference),輸出參數(by output),數組參數(by array)。傳值參數無需額外的修飾符,傳址參數需要修飾符ref,輸出參數需要修飾符out,數組參數需要修飾符params。傳值參數在方法調用過程中如果改變了參數的值,那麼傳入方法的參數在方法調用完成以後並不因此而改變,而是保留原來傳入時的值。傳址參數恰恰相反,如果方法調用過程改變了參數的值,那麼傳入方法的參數在調用完成以後也隨之改變。實際上從名稱上我們可以清楚地看出兩者的含義--傳值參數傳遞的是調用參數的一份拷貝,而傳址參數傳遞的是調用參數的內存地址,該參數在方法內外指向的是同一個存儲位置。

方法參數上的 ref 方法參數關鍵字使方法引用傳遞到方法的同一個變量。當控制傳遞迴調用方法時,在方法中對參數所做的任何更改都將反映在該變量中。

若要使用 ref 參數,必須將參數作爲 ref 參數顯式傳遞到方法。ref 參數的值被傳遞到 ref 參數。

傳遞到 ref 參數的參數必須最先初始化。將此方法與 out 參數相比,後者的參數在傳遞到 out 參數之前不必顯式初始化。

屬性不是變量,不能作爲 ref 參數傳遞。

如果兩種方法的聲明僅在它們對 ref 的使用方面不同,則將出現重載。但是,無法定義僅在 ref 和 out 方面不同的重載。

out

方法參數上的 out 方法參數關鍵字使方法引用傳遞到方法的同一個變量。當控制傳遞迴調用方法時,在方法中對參數所做的任何更改都將反映在該變量中。

當希望方法返回多個值時,聲明 out 方法非常有用。使用 out 參數的方法仍然可以返回一個值。一個方法可以有一個以上的 out 參數。

若要使用 out 參數,必須將參數作爲 out 參數顯式傳遞到方法。out 參數的值不會傳遞到 out 參數。

不必初始化作爲 out 參數傳遞的變量。然而,必須在方法返回之前爲 out 參數賦值。

屬性不是變量,不能作爲 out 參數傳遞。


網上有很多文章說ref 只傳值,out傳地址等等這種說法,好像不是非常的準確。以下是我做的實例代碼,大家可以去試試:

 public int  RefValue(int i,ref int j)
        {
            int k = j;
            j =222;
            return i+k;
        }

     
        public int OutValue(int i, out int j)
        {
            j = 222;
            return i + j;
        }

        private void cmdRef_Click(object sender, EventArgs e)
        {
            int m = 0;
            MessageBox.Show(RefValue(1, ref m).ToString());
            MessageBox.Show(m.ToString());
        }

        private void cmdOut_Click(object sender, EventArgs e)
        {
            int m;
            MessageBox.Show(OutValue(1, out m).ToString());
            MessageBox.Show(m.ToString());
        }
借網上總結的一句話說,ref是有進有出,而out是隻出不進。

代碼示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Parameter
{
    class Program
    {
        static void Main(string[] args)
        {
            Test1 test1 = new Test1();
            test1.MainClaee1();

            Test2 test2 = new Test2();
            test2.MainClass2();

            Test3 test3 = new Test3();
            test3.MainClaee3();

            Test4 test4 = new Test4();
            test4.MainClass4();

            Console.ReadKey();
        }
    }

    #region 值類型參數的按值傳遞
    class Test1
    {
        public void MainClaee1()
        {
            int a = 1;
            Change(a);
            Console.WriteLine(a + "\n");
        }
        private static void Change(int b)
        {
            b = b + 2;
            Console.WriteLine(b);
        }
    }
    #endregion 值類型參數的按值傳遞

    #region 引用類型參數的按值傳遞
    class Test2
    {
        public void MainClass2()
        {
            ArgsByRef abr = new ArgsByRef();
            Change(abr);
            Console.WriteLine(abr.i + "\n");
        }
        private static void Change(ArgsByRef abr)
        {
            abr.i = 20;
            Console.WriteLine(abr.i);
        }
    }

    class ArgsByRef
    {
        public int i = 10;
    }
    #endregion 引用類型參數的按值傳遞

    #region string類型作爲引用類型的特殊性
    class Test3
    {
        private int i = 10;
        public void MainClaee3()
        {
            string str = "Old Str";

            Test3 test3 = new Test3();
            Change(test3);
            ChangeStr(str);
            Console.WriteLine(str);
            Console.WriteLine(i + "\n");

        }
        private static void Change(Test3 t)
        {
            t = new Test3();
            t.i = 20;
        }
        private static void ChangeStr(string str)
        {
            str = "New Str";
            Console.WriteLine(str);
        }
    }
    #endregion string類型作爲引用類型的特殊性

    #region 按引用傳遞 ref和out
    class Test4
    {
        public void MainClass4()
        {
            int i = 10;//int i;   ref使用前,必須先賦值
            Console.WriteLine("使用ref傳值:");
            ChangeByRef(ref i);
            Console.WriteLine(i);

            int i2;
            Console.WriteLine("使用out傳值:");
            ChangeByOut(out i2);
            Console.WriteLine(i2);
        }

        private static void ChangeByRef(ref int i)
        {
            i = 20;
            Console.WriteLine(i);
        }
        private static void ChangeByOut(out int i)
        {
            i = 30;
            Console.WriteLine(i);
        }
    }
    #endregion 按引用傳遞 ref和out

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章