日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

C# 引用類型作為函數(shù)參數(shù)時

 herowuking 2015-11-21

在探討本文的主題之前,先來介紹下C#中的值類型和引用類型

眾所周知C#中有值類型和引用類型,值類型有基礎(chǔ)數(shù)據(jù)類型(諸如int,double,bool等)、結(jié)構(gòu)體、枚舉,引用類型有接口、類、委托。

值類型全部在操作系統(tǒng)的??臻g中申請,而引用類型則在操作系統(tǒng)的堆空間中建立對象,然后在??臻g中申請一個指針指向這個對象的地址。

因此C#的引用類型其實就如同C++的指針類型。

 

下面我再來看看函數(shù)傳參的問題。

早在C時代就有函數(shù)參數(shù)傳值和傳地址的概念,請記住在C#中函數(shù)參數(shù)默認(rèn)都是傳值。

  • 對于值類型,函數(shù)是將實參變量的值在??臻g復(fù)制一份然后傳給形參變量。所以在函數(shù)中對形參變量的更改不會對實參變量造成任何影響,因為函數(shù)的形參只是實參的副本。
  • 而對于引用類型,由于實參變量和形參變量都是引用類型,它們都指向內(nèi)存堆中的某一對象的地址,函數(shù)是將實參變量指向的地址值復(fù)制了一份給形參變量,由于形參變量和實參變量指向堆中同一地址,所以在函數(shù)中使用形參變量對所指向?qū)ο笏龅母囊矔趯崊⒆兞恐蟹从吵鰜怼?/li>

 

所以不管是值類型還是引用類型在作為參數(shù)傳進函數(shù)時,其實都是傳的值,只不過引用類型傳的是對象在堆中的的地址罷了。

而且從上面的定義可以看出C#中引用類型的變量用C++來說就相當(dāng)于是該引用類型的指針,比如有類(引用類型)RefClass:

RefClass rc就相當(dāng)于是C++上的RefClass *rc

在C#中使用rc.IntValue++;時,相當(dāng)于C++的rc->IntValue++;

 

因為引用類型在函數(shù)傳參時是傳地址的,所以我腦袋里就形成了一種慣性思維,認(rèn)為只要傳進函數(shù)的是引用類型,那么在函數(shù)中做的任何更改都會反映到實參上。但是我發(fā)現(xiàn)并不完全是這樣,下面給出個例子(注釋內(nèi)容為對應(yīng)等效的C++代碼):

復(fù)制代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RefWarn
{
    class RefClass
    {
        public int IntValue
        {
            get;
            set;
        }
    }

    class Program
    {
        static void AddValue(RefClass prc)//RefClass *prc,prc和傳進來的rc指向同一個RefClass對象的地址
        {
            prc.IntValue++; //prc->IntValue++;
        }

        static void AddValue(ref RefClass prc)//RefClass **prc,prc指向傳進來的rc的地址
        {
            prc.IntValue++;//(*prc)->IntValue++;
        }

        static void ChangeRef(RefClass prc)//RefClass *prc,prc和傳進來的rc指向同一個RefClass對象的地址
        {
            prc = new RefClass() { IntValue = 1000 };
            //prc=new RefClass() { IntValue = 1000 };請注意new關(guān)鍵字在C++中創(chuàng)建的是指向?qū)ο蟮闹羔槻皇菍ο?/font>
        }

        static void ChangeRef(ref RefClass prc)//RefClass **prc,prc指向傳進來的rc的地址
        {
            prc = new RefClass() { IntValue = 1000 };
            //*prc=new RefClass() { IntValue = 1000 };請注意new關(guān)鍵字在C++中創(chuàng)建的是指向?qū)ο蟮闹羔槻皇菍ο?/font>
        }

        static void Main(string[] args)
        {
            RefClass rc = new RefClass() { IntValue = 1 };//RefClass *rc=new RefClass() { IntValue = 1 };請注意new關(guān)鍵字在C++中創(chuàng)建的是指向?qū)ο蟮闹羔槻皇菍ο?/span>
            AddValue(rc);//rc,傳遞指向RefClass對象的指針
            Console.WriteLine("調(diào)用AddValue(rc)后IntValue為:" + rc.IntValue);

            rc.IntValue = 1;
            AddValue(ref rc);//&rc,傳遞指向RefClass對象指針的指針
            Console.WriteLine("調(diào)用AddValue(ref rc)后IntValue為:" + rc.IntValue);

            rc.IntValue = 1;
            ChangeRef(rc);//rc,傳遞指向RefClass對象的指針
            Console.WriteLine("調(diào)用ChangeRef(rc)后IntValue為:" + rc.IntValue);

            rc.IntValue = 1;
            ChangeRef(ref rc);//&rc,傳遞指向RefClass對象指針的指針
            Console.WriteLine("調(diào)用ChangeRef(ref rc)后IntValue為:" + rc.IntValue);
        }
    }
}
復(fù)制代碼

你會發(fā)現(xiàn)在Main函數(shù)中調(diào)用ChangeRef(rc)后,rc并沒有發(fā)生改變,其屬性IntValue的值還是1。

這是為什么?我們先來看看static void ChangeRef(RefClass prc)函數(shù)的結(jié)構(gòu),看看里面都做了什么

static void ChangeRef(RefClass prc)//RefClass *prc,prc和傳進來的rc指向同一個RefClass對象的地址
{
  prc = new RefClass() { IntValue = 1000 };
  //prc=new RefClass() { IntValue = 1000 };
}

可以看到函數(shù)里就是對RefClass 類型的形參引用變量prc重新賦了值。但是最后我們看到這個賦值并沒有反應(yīng)到實參引用變量rc上。原因其實很簡單就像本文開始所說的一樣,由于實參變量pc和形參變量rpc都是引用類型的變量,那么它們實際上是在操作系統(tǒng)??臻g上的兩個指針,只不過指向的是操作系統(tǒng)堆空間上的同一個RefClass 對象。在函數(shù)ChangeRef中對引用變量rpc重新賦值,相當(dāng)于是將棧中的rpc指針重新指向了堆中的另一個RefClass 對象。形參變量rpc指向的地址改變后,并不會對實參變量pc的指向發(fā)生改變,所以pc還是指向函數(shù)ChangeRef(RefClass prc)調(diào)用前的那個RefClass 對象。

 

但是也許你又會問為什么AddValue(rc)執(zhí)行后,函數(shù)對rc做了更改呢?我們來看看AddValue(RefClass prc)函數(shù)

static void AddValue(RefClass prc)//RefClass *prc,prc和傳進來的rc指向同一個RefClass對象的地址
{
  prc.IntValue++; //prc->IntValue++;
}

請注意函數(shù)AddValue并不是更改了實參引用變量rc,它更改的是rc指向的RefClass 對象的屬性,是因為實參變量pc和形參變量rpc都指向同一個RefClass 對象的原理,所以在AddValue里面rpc更改了它所指向RefClass 對象的屬性,也就等于更改了pc指向RefClass 對象的屬性。所以才在執(zhí)行AddValue(rc)后給人一種好像rpc和pc是同一個變量,更改了rpc就等于更改了pc的錯覺。但是請記住這是絕對錯誤的,rpc和pc是兩個完全不同的引用變量,只不過指向的是內(nèi)存中的同一個RefClass 對象。

 

最后我們來探討下有沒有辦法使函數(shù)在傳遞引用類型的參數(shù)時,讓形參完全等于實參呢?能否做到不管對形參是重新賦值還是做更改,都反映到實參上?

答案是肯定就是使用ref關(guān)鍵字

這個關(guān)鍵字用在值類型上的時候,就相當(dāng)于C++的指針類型,比如:

ref int param

就相當(dāng)于C++的

int *param

且該指針指向的就是其對應(yīng)的實參變量

所以在C#中使用聲明為ref的int形參變量param.ToString()時候,相當(dāng)于C++上使用int指針*param.ToString()

所以在使用聲明為ref的int形參param時,就相當(dāng)于是C++上的*param,其操作的就是param指向的那個int變量,即實參。

 

而當(dāng)這個關(guān)鍵字用在引用類型前面的時候,就相當(dāng)于是指向引用類型變量的地址,而前面說過C#引用類型的變量就相當(dāng)于是C++的指針,那么指向引用類型變量的地址也相當(dāng)于就是指向指針的指針。

因為前面說了RefClass rc相當(dāng)于C++的RefClass *rc

那么ref RefClass rc相當(dāng)于C++的RefClass **rc

在C#中使用聲明為ref的RefClass變量rc.ToString()時,當(dāng)于C++上上使用RefClass指針的指針*rc->ToString()

所以在使用聲明為ref的RefClass類型形參rc時,就相當(dāng)于是C++上的*rc(注意*rc還是指針,因為rc是指向指針的指針),其操作的是形參rc指向的那個RefClass類型的引用變量(即rc指向的是實參變量的地址,而不實參變量指向堆空間中對象的地址),即實參。

 

而實參前面的ref相當(dāng)于是C++的&符號即取該變量的地址。

 

所以在函數(shù)形參前加上ref那么形參變量指向的就是實參變量的地址,只不過如果實參類型是值類型,那么形參變量指向的就是該實參變量在操作系統(tǒng)棧中的地址。如果實參是引用類型,那么形參變量指向的也是實參變量在操作系統(tǒng)棧中的地址,只不過該實參變量又指向?qū)ο笤诓僮飨到y(tǒng)堆中的地址。所以無論是引用類型還是值類型,只要在其作為形參時在前面加上ref,那么形參變量都是指向?qū)崊⒆兞康闹羔?,則操作形參變量就等于是在操作實參變量。

 

最后一定要清楚在引用類型做函數(shù)形參時,加上ref和不加ref的不同。

還是拿RefClass rc來舉例:

  • 不加ref時形參變量rc是指向操作系統(tǒng)堆空間中RefClass對象的指針,其和實參變量共同指向這個RefClass對象的地址,形參變量和實參變量之間無直接關(guān)系,通過形參變量rc對RefClass對象所做的更改,同樣也可以通過實參變量看到,但是對形參變量rc本身做更改(比如改變其指向的地址),并不會對實參變量產(chǎn)生任何影響。
  • 加上ref時形參變量rc指向的是實參變量的地址,rc直接指向?qū)崊⒆兞?,由于實參本身就是指針,所以rc就是指向指針的指針,*rc就完全等于實參變量。對更改形參便變量*rc就是更改實參變量。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多