引用傳遞的一點解疑

2022-01-29 21:14:25 字數 3635 閱讀 2009

當乙個方法的引數為引用型別,那麼它的實參到形參的傳遞一定是引用傳遞,方法內部形參的改變會使得呼叫方傳入的實參的改變。

下面先說說背景吧。之前用c# winform寫了乙個小程式,其中乙個父窗體是入庫操作,這裡面有乙個按鈕是增加入庫商品,乙個datagridview是顯示本次已經選擇的要入庫的商品,最後點選入庫按鈕將選擇的商品一併入庫。datagridview的資料來源用的是datatable,而選擇入庫商品我放到了另乙個窗體裡,也就是子窗體(模態窗體),在子窗體裡有個下拉列表框用於選擇某個商品,然後點選確定按鈕,下拉列表框中當前選定的商品就被增添到要入庫的商品中。

接下來我說一下之前我是如何實現的。首先在父窗體中例項化乙個datatable型別變數,變數名假設為dtproduct。然後在增加入庫商品按鈕的點選事件中例項化子窗體,並以模態窗體方式顯示出來。而例項化子窗體時使用子窗體含有乙個datatable型別引數的建構函式。最後我把父窗體例項化的dtproduct作為引數傳入子窗體的建構函式中。而點選子窗體的確定按鈕時,我希望將使用者選擇的商品新增到傳入的dtproduct,這時父窗體裡的實參也會相應改變,於是乎我就在建構函式的引數前加了乙個ref修飾符。當時圓滿實現需求,並未多想什麼。

最近突然想到引用型別的賦值,不是將堆裡的值賦給新的變數,而是把棧裡對堆的引用賦值給新的變數。於是乎就想到了方法傳參這個問題,引用型別傳參實際也是將實參給形參賦值,那麼我用的datatable型別本身就是引用型別,傳遞的就應該是引用,而不是值,為什麼還要加ref修飾符呢?帶著這個問題查閱了一些關於方法實參和形參的部落格文章,並通過實際測試得到前面的結果。

1

using

system;

2using

system.collections.generic;

3using

system.linq;

4using

system.text;

5using

system.data;67

namespace

console1310318"

,originaldt.rows.count);

24 datatable newdt =p.test(originaldt);

25 console.writeline("

執行完方法後的行數:

", originaldt.rows.count);

26 console.writeline("

方法返回值的行數:

", newdt.rows.count);

27console.readkey();28}

29datatable test(datatable dt)30"

, dt.rows.count);

37return

dt;38}39

}40 }

引用型別引用傳遞測試例子

執行後發現:「執行完方法後實參的行數」為2;而「方法內部形參的行數」也為2。也就是不需要加ref修飾符,引用型別的引用傳遞也是會通過改變形參而改變實參。因為傳遞的是引用(可以看成是位址),改變的是引用的堆裡面的值。前面推薦的部落格裡提到4種傳遞,其中引用型別一般肯定是引用傳遞(因為引用型別不在棧裡存值,而是存對於堆的引用),但是有個例外,就是字串,字串是引用型別,但是由於字串具有不可變性,所以字串是值傳遞。而值型別有兩種,一種是值傳遞,另一種是引用傳遞。如果值型別要實現引用傳遞,就要加ref修飾符。

到這裡算是比較了解方法的實參到形參的傳遞了。如果上面有說的不對的地方,歡迎指正,謝謝!

關於方法的引數傳遞補遺:

最近學習了《c#本質論》這本書,受益匪淺。並且對之前提出的「方法的引數傳遞」這個問題有了更深了了解,在這裡補充說明一下。首先,方法的引數傳遞都是值傳遞(注意不是值型別,寫上面的文章時並沒有很清晰地理解值傳遞和值型別兩個概念),如果方法的引數傳遞想成為引用傳遞,就必須加修飾符ref或out。而引數又有資料型別,資料型別分為值型別和引用型別。值型別/引用型別與值傳遞/引用傳遞需要分開理解,下面舉個例子:

int i=3;  int j=i;//這是值型別的賦值,由於值型別存放在棧上,所以變數i和變數j是在不同的棧位址上,且每個位址中存放的值均為3

datatable dt=new datatable();  datatable dtnew=dt;//這是引用型別的賦值,由於引用型別存放在堆(託管堆)上,所以datatable的乙個例項/物件是在堆中,而變數dt是在乙個棧位址上存放對堆中物件的位址的引用,變數dtnew也是對堆中物件的位址的引用

這是如果更改變數i的值,是不會影響變數j中的值,而更改變數dtnew(datatable物件)的值時,更改的是堆中的物件,因此引用同乙個物件的變數dt也會顯示出新的值。(不知道這麼說,是否說明白了)

方法有實參和形參一說,可以將它理解成兩個變數,在呼叫方法時,實際上是將實參的值賦值給形參。當不帶ref或out修飾符時,方法的引數傳遞為值傳遞,因此可以將實參賦值給形參的過程想象成值型別的賦值過程(注意,這裡兩個過程並不一樣,只是用於理解)。只需要記住一點,方法呼叫時是將實參的棧位址中的值賦值給形參的棧位址的值。如果方法引數型別為值型別,比如:

void demomethod(int paramvalue){}

static void main()

int v=3;

demomethod(v);

這裡記憶體中存在兩個變數,乙個是v,乙個是paramvalue,v的棧位址中存放的就是3,然後將3複製乙個副本,存放到paramvalue的棧位址中,所以當在方法中更改形參的值,方法外實參的值是不會受到影響。如果方法引數型別不幸為引用型別,比如:

void demomethod(datatable paramvalue){}

static void main()

datatable dt=new datatable();

demomethod(dt);

這裡記憶體中存在兩個變數和乙個物件,物件存放在堆中,且其堆位址假設為0x1304c,那麼dt的棧位址中存放的就是0x1304c值,然後將這個值複製乙個副本,存放到paramvalue的棧位址中。這時若在方法中更改物件中的某個屬性,比如增加乙個datarow,方法外的實參引用的是同乙個物件,對應的datarow.count的值也會增加1。這看起來跟引用型別的賦值很像嘛,那為什麼叫值傳遞呢?為了理解這個問題,再舉個例子:

datatable dt=new datatable();  datatable dtnew=dt;  dt=new datatable();//這時更改dtnew中的某個屬性,比如增加乙個datarow,那麼dt中的datarow.count會是1嗎?當然顯然不是。因為堆中已經有兩個物件了,而dtnew和dt的棧位址中所引用的物件位址不相同。好,回到之前的問題,方法呼叫之所以叫值傳遞,就是因為當在方法中對形參賦值乙個新物件時,實參引用的仍然是舊物件,這是在方法中對形參做任何修改,都不會影響到實參,因為實參的棧位址中存放的物件引用位址與形參的棧位址中不相同了。這裡不舉例子了,不知道這麼說是否清楚。

我們在寫方法時有一種需求就是,方法中對形參的修改要反應到呼叫方傳入的實參的值,所以這裡出現引用傳遞。當通過引用傳遞方式傳遞乙個值型別資料,那麼當方法中將值由3改為5,那麼方法外的實參也被改為5,這個很好理解。如果通過引用傳遞方式傳遞乙個引用型別資料呢?那麼即使方法中將形參重新例項化乙個物件,方法外的實參也隨之指向方法中例項化的新物件。

最後總結一下,就是值傳遞傳遞的是棧位址中的值,而引用傳遞傳遞的是棧位址的引用。(補充完畢,2023年5月30日)

offset 和 零點的一點解釋

目錄 一 offset 1.0.0第三個關節沒有offset的零點位置 1.0.1第三個關節轉動pi 6 1.1.0第三個關節有offset的零點位置 1.1.1 第三個關節轉動pi 6 二 零點 2.0什麼是零點丟失?2.1是什麼原因造成了零點的丟失?2.2零點丟失之後重新校正,對模型的精度有影響...

系統很慢一點解決心得

hp家用機,amd athon的cpu,昨天關機後把插座電源開關給關了,今天開起來時,發現電腦時間不對了,應該是cmos電池沒電了那種狀況。然後發現電腦執行巨慢,滑鼠拖動都是停停頓頓的。把那個360啊,防毒啊都關了,也是這樣。進安全模式,winpe也是巨慢,重灌系統ghost要20來分鐘。裝置管理沒...

關於python的 init 方法的一點解釋

以下是 例子 class parent object def init self self.name yy def override self print parent override def implicit self print parent implicit def altered self...