C#教程第6講方法2(學習筆記)

第6講  方法2
視頻講師:陳廣老師

 大家好,今天我們來講一下方法的傳遞機制。方法的傳遞機制分爲三種:
 PPT:值參數(Value Parameter)
      方法名稱(參數類型 參數名稱 [,參數類型 參數名字])
      引用參數(Reference Parameter)
      方法名稱(ref 參數類型 參數名稱 [,ref 參數類型 參數名字])
      輸出參數(Output Paramenter)
      方法名稱(out參數類型 參數名稱 [,out 參數類型 參數名字])
 這三種參數的區別在哪裏呢,下面我們用簡單的例子來講述:

我們進行編譯代碼,看看i的值是多少,如圖:

我們看i的值還是0,沒有改變。換句話說,雖然i作爲ValueMethod的參數傳遞了進去,在參數內執行了i++,但是這個函數的執行並沒有改變i的值,i還是等於0。這意味着Main函數裏的局部變量i跟ValueMethod方法裏的參數i並不是同一個變量。雖然ValueMethod給i加了 1,但是它並不影響Main函數裏局部變量i。接下來我們寫個帶引用參數的方法。

好,我們再來看看帶引用參數方法ReferenceMethod,Main函數中j的值是多少~

我們看j=1,這一次Main函數中局部變量j它的值改變了由於ReferenceMethid這個方法它的參數前面帶了個ref關鍵字,使得它可以直接操作傳入的參數j,從而使得j作出改變。下面我們再聲明一個帶輸出參數的方法。

進行編譯執行程序,效果如下:

k的值也爲1。換句話說使用引用參數和使用out參數所得出的結果都是一樣的。下面我們用圖例來講解三種參數的區別:
    首先是值參數的調用。

    在Main函數中聲明瞭一個變量i,這時候在內存的堆棧中就會專門爲i開闢出一塊新的內存空間,並存放它。接下來執行了ValueMethod方法並把i作爲參數傳遞進去,而在執行ValueMethod方法時,它會在內存堆棧中開闢出另一塊區域,並把i的值拷貝過來,賦給方法中的i值。接着就對這個另外開闢內存空間的i進行操作。導致方法中的i被加1,而Main函數中的i並沒有做任何的改變,最後一句Console.WriteLine("i="+i)打印i的值自然它還是0。
    接下來我們再來看引用參數的調用。

    在Main函數中聲明瞭變量j,使得堆棧中開闢了一個新的空間來存放j,這時候調用了ReferenceMethod方法,並把j作爲參數傳遞了進去,而由於ref關鍵字的存在,使得這次傳遞的是一個內存中的指針。也就是說,這次它會告訴ReferenceMethod方法我傳入參數的地址在哪,你直接調用它就行了。隨意ReferenceMethod方法直接通過這個指針找到參數i在內存中的地址而去操縱它。也就是說Main函數中的j跟ReferenceMethod函數中的i它所指向的是同一塊內存地址,他們的變量是一樣的。所以當ReferenceMethod方法中給參數i加1,直接導致了j的變化,因爲他們所指向的是同一個內存地址。最後一行打印本地變量j,由於j的值作了改變,所以呢打印出來的是1。
    我們再來看一下輸出參數的調用。

    輸出參數的調用跟引用參數的調用非常類似,它所傳送的也是一塊內存地址。而OutputMethod方法中的參數找到這塊內存地址,並對它裏面的i進行操作。唯一不同的是,在輸出參數中必須對參數初始化。
    我們來對原來的代碼做些修改,我們將Main函數中的j不進行初始化,而是在ReferenceMethod中將其初始化,我們看看能否通過編譯。

    不能通過,提示了使用了未賦值的局部變量j。換句話說,在調用ref方法之前必須對參數進行初始化,而不是在方法內部進行初始化。
    輸出參數和引用參數的區別:
    從CLR(公共語言運行時)的角度看,關鍵字out和關鍵字ref是等效的,這就是說,無論使用哪個關鍵字,都會生成相同的數據和IL代碼(我們知道.NET中使用的是中間代碼,無論你使用C#還是VB.NET來編寫代碼都會生成IL代碼)。但是, C#編譯器將兩個關鍵字區別對待,在C#中,這兩個關鍵字的區別在於哪個方法負責初始化引用對象。 如果方法的參數標記爲out,那麼調用者不希望在調用方法之前初始化對象,被調用的方法不能讀取對象的值,而且被調用的方法必須在返回之前爲對象賦值。如果方法的參數標記爲ref,那麼調用者必須在調用方法之前首先初始化參數的值,被調用的方法可以讀取參數或爲參數賦值。
    接下來,我們講一下向方法傳遞可變數量的參數。爲了將方法聲明爲可以接受可變數量參數的方法,使用params關鍵字。代碼如下:

我們進行編譯執行,效果如下:

我們看到屏幕上正確的輸出了結果。我們返回來看代碼,通過params關鍵字可以向方法傳遞可變數量參數。
    下面我們嘗試數組傳遞的方式。代碼如下:

我們編譯代碼看下效果,大家可以在之前判斷下結果會是什麼。效果如下:

    打印出來的是0,1,2,3。我們回過頭來看下程序,int[] arr = { 100, 200, 300, 400 };arr初始化100,200,300,400。可是經過PrintArr方法的調用以後,它的值改變了,改變成方法裏所賦的值。大家可能會覺的奇怪了,PrintArr方法中的參數我也沒定義成ref呀,爲什麼數組的值會改變呢?這是因爲數組是一個引用類型的變量,這裏我們就來講一下值類型和引用類型的差別。
    值類型和引用類型:
    類型分爲值類型和引用類型。類型區分爲這兩大類的主要原因是在於執行性能與內存資源管理的不同。由於值類型變量直接在堆棧(stack)中存儲該類型的值,此類類型的內存的使用上以及訪問的效能上比引用類型更好。因爲引用類型變量存放的是指向實際對象的指針,因此訪問對象時必須對進行一次內存引用的操作方可獲取數據。且引用類型的對象必須分配多餘的內存來存放虛函數指針及線程同步塊,對於內存的需求較大。而使用引用類型的優點是回收站會自動替你管理分配在託管堆(Managed Heap)當中的內存。
    我們來看下面的圖標,清楚的比較它們之間的不同。


值類型 引用類型
變量中存放     真正的數據   
指向數據的引用指針
內存空間分配
堆棧(stack) 託管堆(Managed Heap)
內存需求 一般來說較少
較大
執行效能 較快 較慢
內存釋放時間點
執行超過定義變量的作用域 由回收站負責回收
可以爲null 不可     可以


當然這裏還少說了一個值類型的缺點,在特定的條件下值類型需要進行裝箱和拆箱的操作,這個會使得它的執行效能變慢。
我們來看下面的圖:

    值類型,當我們聲明一個變量i的時候,就會在堆棧中分配一個新的空間,並把0存放在裏面。也就是說值類型變量的值直接存放在堆棧中的。當我們聲明一個數組i並把它初始化爲1,2,3之後,這3個元素就會被存放在託管堆中,而堆棧中存放的指向這個託管堆的地址。當我們把數組作爲參數賦給方法之後,它所傳遞的其實是堆棧中的地址,而方法接受到這個地址以後,就根據這個地址到託管堆中直接操作這個數組裏的元素了。這也是爲什麼我們把數組作爲值參數傳遞給方法以後他的值仍會改變的原因。
    當然這裏也有意外,比如說字符串。字符串也是一個引用類型的變量。下面我們來做一下實驗。

進行編譯代碼,效果如下:

    在這裏屏幕上打印的是123456,也就是說Main函數值中s的值並沒有被改變,這是爲什麼呢?s本身是引用類型的變量,它爲什麼不被改變呢?這是由string對象特點來決定的,string對象最重要的一個事實就是它是不可變的。也就是說,字符串在創建之後就再也不能改變,其中包括變長變短。或者修改其中的任何字符,由於字符串這個特點使得在調用SetStr方法時,SetStr會在堆中創建另一個s的副本來操作它。使得Main函數中的s值並沒有改變。
    好,這節課的內容就講到這裏。

由快樂喬巴聽課摘寫筆記
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章