C#篇(三)——函數傳參之引用類型和值類型


首先應該認清楚在C#中只有兩種類型:
1、引用類型(任何稱為“類”的類型)
2、值類型(結構或枚舉)

先來認識一下引用類型和值類型的區別:

函數傳參之引用類型:

1、先來一個簡單的引用類型傳參的實例:

//使用了C#6.0的一個新特性:using static System.Console;
	class Program
    {
        static void StartTest1(string test)
        {
            test = "test2";
            WriteLine(test);//輸出:"test2"
        }

        static void StartTest2(string test)
        {
            test = null;
            WriteLine(test);//輸出:(空白)
        }

        static void Main(string[] args)
        {
            string test = "test1";
            WriteLine(test);//輸出:"test1"

            StartTest1(test);
            WriteLine(test);//輸出:"test1"

            StartTest2(test);
            WriteLine(test);//輸出:"test1"
        }
    }

輸出結果:

test1
test2
test1

test1

結果分析:

首先明白字符串(string)類型是引用類型,但改變了它的值之后,並沒有影響到函數外面那個實參的值,這可能與大家的常識有點相違背,因為我們都知道若是變量以"引用傳遞"的方式傳遞,那么調用的方法可以通過更改其參數值,來改變調用者的變量值,但這里有一點需要說明的是:"引用傳遞"不是等價於引用類型傳參,這是很多人的誤解的地方。其實在C#當中,引用類型和值類型默認都是以“傳值”的方式傳遞數值(引用)的(引用類型的值就是引用(類似索引或地址),而不是對象本身)。
請看下圖詳細分析:

2、再來一個略微復雜的引用類型傳參的實例:

 	class Program
    {
        static void StartTest1(StringBuilder test)
        {
            test.Append("test2");
            WriteLine(test);//輸出:"test1test2"
        }

        static void StartTest2(StringBuilder test)
        {
            test = null;
            WriteLine(test);//輸出:(空白)
        }

        static void Main(string[] args)
        {
            StringBuilder test = new StringBuilder();
            test.Append("test1");
            WriteLine(test);//輸出:"test1"

            StartTest1(test);
            WriteLine(test);//輸出:"test1test2"

            StartTest2(test);
            WriteLine(test);//輸出:"test1test2"
            ReadKey();
        }
    }

輸出結果:

test1
test1test2
test1test2

test1test2

結果分析:

StringBuilder和string同樣是引用類型,那為什么最終的StringBuilder類型值改變了呢?其實這里要糾正一下,真正改變的不是StringBuilder類型值(也就是引用的值),而是引用指向的字符數組引用指向的對象值改變了。在StringBuilder類里面封裝了一個字符數組(最終的輸出的就是這個字符數組,而那些操作也是對這個字符數組進行操作)。

結合上面兩個實例,對於引用類型傳參,從這里可以得出一個小結論:

1、在函數里面,若直接改變的是引用的值(也就是地址),那么之后的操作都不會影響到函數外面的那個變量
2、在函數里面,若直接改變的是引用指向的對象(值類型)的值(甚至更深層次的對象的值),那么就會影響到函數外面的變量

所以區分清楚改變的是引用的值還是引用指向的對象(值類型)的值是關鍵。

3、再來一個綜合的引用類型傳參的實例:

    class Program
    {
        class Test
        {
            public int index;//值類型
            public StringBuilder builder;//引用類型
            public string Result{
                get{return $"{index}:{builder.ToString()}";}
            }
        }
        static void StartTest(Test test)
        {
            test.index++;
            test.builder.Append("test2");
            WriteLine(test.Result);//輸出:"2:test1test2"

            test.index = new int();
            test.builder = new StringBuilder();
            test.builder.Append("test3");
            WriteLine(test.Result);//輸出:"0:test3"
        }
        static void Main(string[] args)
        {
            Test test = new Test {
                index = 0,
                builder = new StringBuilder()
            };
            test.index++;
            test.builder.Append("test1");
            WriteLine(test.Result);//輸出:"1:test1"

            StartTest(test);
            WriteLine(test.Result);//輸出:"0:test3"
        }
    }

輸出結果:

1:test1
2:test1test2
0:test3
0:test3

結果分析:

[若是能夠明白1和2中的分析,這個應該沒有問題的]

函數傳參之值類型:
簡單的值類型傳參這里就不演示了,來一個含有引用類型的值類型傳參實例(只是將上例中的struct改為了class,這樣好做對比):

    class Program
    {
        struct Test
        {
            public int index;//值類型
            public StringBuilder builder;//引用類型
            public string Result{
                get{return $"{index}:{builder.ToString()}";}
            }
        }
        static void StartTest(Test test)
        {
            test.index++;
            test.builder.Append("test2");
            WriteLine(test.Result);//輸出:"2:test1test2"

            test.index = new int();
            test.builder = new StringBuilder();
            test.builder.Append("test3");
            WriteLine(test.Result);//輸出:"0:test3"
        }
        static void Main(string[] args)
        {
            Test test = new Test {
                index = 0,
                builder = new StringBuilder()
            };
            test.index++;
            test.builder.Append("test1");
            WriteLine(test.Result);//輸出:"1:test1"

            StartTest(test);
            WriteLine(test.Result);//輸出:"1:test1test2"
        }
    }

輸出結果:

1:test1
2:test1test2
0:test3
1:test1test2

結果分析:

首先應該明白,值類型以"傳值"方式傳遞時,是一種淺拷貝,所以對於引用類型,只是復制了引用的值,副本(形參)中的引用指向的對象還是同一個。其他的自己分析應該明白。

結論:
1、無論是引用類型還是值類型,永遠不會傳遞對象本身。涉及到一個引用類型時,要么以“引用傳遞”的方式(使用了ref或out關鍵字)傳遞變量,要么以“傳值”的方式傳遞參數值(引用)。所以,通常函數傳參(不論是引用類型還是值類型),都是以“傳值”的方式傳遞的,只是要明白引用類型的值是引用本身(相當於一個索引或地址,而這個索引或地址最終指向的才是對象本身)。

2、“引用方式”傳遞與“傳值”傳遞方式最大的區別就是“引用方式”要使用ref或out關鍵字修飾,所以以這個為標准去區分函數傳參的方式(而不是以類型是引用類型還是值類型)。

3、對於傳入函數的引用類型變量,最終會不會受到函數內部修改的影響,需要區分清楚函數內部改變的是引用的值還是引用指向的對象(值類型)的值。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM