狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

C#.net core 基礎 - 值傳遞、引用傳遞

freeflydom
2025年1月2日 8:39 本文熱度 1644

不知道你在開發過程中有沒有遇到過這樣的困惑:這個變量怎么值被改?這個值怎么沒變?

今天就來和大家分享可能導致這個問題的根本原因值傳遞 vs 引用傳遞。

在此之前我們先回顧兩組基本概念:

值類型 vs 引用類型

值類型: 直接存儲數據,數據存儲在棧上;

引用類型: 存儲數據對象的引用,數據實際存儲在堆上。

形參 vs 實參

形參: 即形式參數,表示調用方法時,方法需要你傳遞的值。方法聲明定義了其形參。也就是說在定義方法時,緊跟在方法名后面括號中的參數列表就是形參。

實參: 即實際參數,表示調用方法時,你傳遞給方法形參的值。調用代碼在調用過程時提供實參。也就是說在調用方法時,緊跟在方法名后面括號中的參數列表就是實參。

再來回顧一下值類型和引用類型在內存中是怎么存儲的呢?

對于值類型變量的值直接存儲在棧中,如下圖的int a=10,10就直接存在棧空間中,而其棧空間對應的內存地址為0x66666668;對于引用類型變量本身存儲的是實例對象的引用,即實例對象在堆中的實際內存地址,因此引用類型變量是存儲其實例對象的引用于棧上,如下圖中變量Test a在棧中實際存儲的是實例對象Test a在堆中的內存地址0x88888880,而棧空間對應的內存地址為0x66666668。

棧也是有內存地址的,這一點很重要,無論棧空間上存儲的是值還是引用地址,這個棧空間本身也有自己對應的內存地址。

什么是值傳遞?什么是引用傳遞?

值傳遞:如果變量按值傳遞給方法,則會把變量的副本傳遞給方法。對于值類型則把變量的副本傳遞給方法,對于引用類型則把變量的引用的副本傳遞給方法。因此被調用方法參數會創建一個新的內存地址用于接收存儲變量,因此在方法內部對變量修改并不會影響原來的值。

引用傳遞:如果變量按引用傳遞給方法,則會把變量的引用傳遞給方法,對于值類型則把變量的棧空間地址傳遞給方法,對于引用類型則把變量的引用的棧空間地址傳遞給方法。因此被調用方法參數不會創建一個新的內存地址用于接收存儲變量,意味著形參與實參共同指向相同的內存地址,因此在方法內部修對變量修改會影響原來的值。

上面的描述可能有點拗口,下面我們在基于值類型、引用類型、值傳遞、引用傳遞各種組合進行一個詳細說明。

01、值類型按值傳遞

當值類型按值傳遞時,調用者會把值類型變量的副本傳遞給方法,因此被調用方法參數會創建一個新的內存地址用于接收存儲變量,因此當在方法內部對參數進行修改時并不會影響調用者調用處的值類型變量。

傳遞值類型變量的副本就是相當于在棧上,又復制了一個同樣的值,而且內存地址還不一樣,所以互不影響。如下圖把a賦值給b,則b直接新開辟了一個棧空間,雖然a和b都是10,但是它們在不同的地址空間中,因此如果他們各自被修改了,也互不影響。

下面我們寫個例子演示一下,這個例子就是定義個變量a并賦值,然后調用一個方法此方法內對傳進來的參數a進行加1,具體代碼如下:

public static void ValueByValueRun(){
    var a = 10;
    Console.WriteLine($"調用者-調用方法前 a 值:{a}");
    ChangeValueByValue(a);
    Console.WriteLine($"調用者-調用方法后 a 值:{a}");
}public static void ChangeValueByValue(int a){
    Console.WriteLine($"    被調用方法-接收到 a 值:{a}");
    a = a + 1;
    Console.WriteLine($"    被調用方法-修改后 a 值:{a}");
}

運行結果如下:

通過代碼執行結果可以發現,方法內對變量的修改已經生效,但是不沒有影響到調用者調用處的變量值。

02、引用類型按值傳遞

當引用類型按值傳遞時,調用者會把引用類型變量的引用副本傳遞給方法,因此被調用方法參數會創建一個新的內存地址用于接收存儲變量,而對于一個引用類型變量來說其本身存儲的就是引用類型實例對象的引用副本,而方法接收到的也是此變量引用的副本,所以調用者參數和被調用方法參數是引用了同一個實例對象的兩個引用副本。如下圖Test a可以理解為調用者傳的實參,Test b可以理解為被調用方法定義的形參,這兩個參數都只是指向堆中Test a的引用副本。

因此可以得出兩個結論:

1、變量a和b都是指向實例對象Test a的引用,所以無論變量a或b,只要有一個更新了實例成員則另一個變量也會同步發生變化。

2、雖然變量a和b都是指向實例對象Test a的引用,但是他們存儲在棧上的內存地址卻不同,因此如果他們各種重新分配實例也就是new一個新對象,則另一個變量無法感知到還是保持原因狀態不變。

我們先用代碼說明第一個結論:

public static void ChangeReferenceByValueRun(){
    var a = new Test
    {
        Age = 10
    };
    Console.WriteLine($"調用者-調用方法前 a.Age 值:{a.Age}");
    ChangeReferenceByValue(a);
    Console.WriteLine($"調用者-調用方法后 a.Age 值:{a.Age}");
}
public static void ChangeReferenceByValue(Test a){
    Console.WriteLine($"    被調用方法-接收到 a.Age 值:{a.Age}");
    a.Age = a.Age + 1;
    Console.WriteLine($"    被調用方法-修改后 a.Age 值:{a.Age}");
}

運行結果如下:

可以看到被調用方法中a實例對象的Age屬性發生變化后,調用者中變量也同步發生了變化。

對于第二個結論我們這樣論證,在方法中直接對參數new一個新對象,看看原變量是否發生變化,代碼如下:

public static void NewReferenceByValueRun(){
    var a = new Test
    {
        Age = 10
    };
    Console.WriteLine($"調用者-調用方法前 a.Age 值:{a.Age}");
    NewReferenceByValue(a);
    Console.WriteLine($"調用者-調用方法后 a.Age 值:{a.Age}");
}
public static void NewReferenceByValue(Test a){
    Console.WriteLine($"    被調用方法-接收到 a.Age 值:{a.Age}");
    a = new Test
    {
        Age = 100
    };
    Console.WriteLine($"    被調用方法-new后 a.Age 值:{a.Age}");
}

執行結果如下:

可以發現當在方法中對變量執行new操作后,調用者處的變量并沒有發生變化。

為什么會這樣呢?因為對于引用類型來說,形參和實參是對引用類型的實例對象引用的兩個副本,而這兩個副本存儲在棧上又分別在不同的內存地址空間上,而new主要就是重新分配內存,這就導致形參變量a=new后,棧上形參變量a指向了Test新的實例對象的引用,而實參變量a還是保持原有實例對象引用不變。

如下圖所示。

03、值類型按引用傳遞

當值類型按引用傳遞時,調用者會把值類型變量對應的棧空間地址傳遞給方法,因此被調用方法參數不會創建一個新的內存地址用于接收存儲變量,因此當在方法內部對參數進行修改時并同樣會影響調用者調用處的值類型變量。

傳遞值類型變量對應的棧空間地址就意味著形參與實參共同指向相同的內存地址,所以才導致對形參修改時,實參也會同步發生變化。

我們用一個小例子演示一下:

public static void ValueByReferenceRun(){
    Console.WriteLine($"值類型按引用傳遞");
    var a = 10;
    Console.WriteLine($"調用者-調用方法前 a 值:{a}");
    ChangeValueByReference(ref a);
    Console.WriteLine($"調用者-調用方法后 a 值:{a}");
}
public static void ChangeValueByReference(ref int a){
    Console.WriteLine($"    被調用方法-接收到 a 值:{a}");
    a = a + 1;
    Console.WriteLine($"    被調用方法-修改后 a 值:{a}");
}

執行結果如下:

可以發現調用者處的值類型變量已經發生改變。

04、引用類型按引用傳遞

當引用類型按引用傳遞時,調用者會把引用類型變量對應的棧空間地址傳遞給方法,因此被調用方法參數不會創建一個新的內存地址用于接收存儲變量,因此當在方法內部對參數進行修改時并同樣會影響調用者調用處的引用類型變量。

傳遞引用類型變量對應的棧空間地址就意味著形參與實參共同指向相同的內存地址,因此對形參修改時,實參也會同步發生變化,而且這個里的修改不單單指修改實例成員,還包括new一個新實例對象。

下面我們看一個修改實例成員的例子:

public static void ChangeReferenceByReferenceRun(){
    Console.WriteLine($"引用類型按引用傳遞 - 修改實例成員");
    var a = new Test
    {
        Age = 10
    };
    Console.WriteLine($"調用者-調用方法前 a.Age 值:{a.Age}");
    ChangeReferenceByReference(ref a);
    Console.WriteLine($"調用者-調用方法后 a.Age 值:{a.Age}");
}
public static void ChangeReferenceByReference(ref Test a){
    Console.WriteLine($"    被調用方法-接收到 a.Age 值:{a.Age}");
    a.Age = a.Age + 1;
    Console.WriteLine($"    被調用方法-修改后 a.Age 值:{a.Age}");
}

執行結果如下:

再看看new一個新對象的例子:

public static void NewReferenceByReferenceRun(){
    Console.WriteLine($"引用類型按引用傳遞 - new 新實例");
    var a = new Test
    {
        Age = 10
    };
    Console.WriteLine($"調用者-調用方法前 a.Age 值:{a.Age}");
    NewReferenceByReference(ref a);
    Console.WriteLine($"調用者-調用方法后 a.Age 值:{a.Age}");
}
public static void NewReferenceByReference(ref Test a){
    Console.WriteLine($"    被調用方法-接收到 a.Age 值:{a.Age}");
    a = new Test
    {
        Age = 100
    };
    Console.WriteLine($"    被調用方法-new后 a.Age 值:{a.Age}");
}

執行結果如下:

另外string是一個特殊的引用類型,string類型變量的按值傳遞和按引用傳遞和值類型是一致的,也就是要把string類型當值類型一樣看待就行。string類型的特殊性我們后面會單獨具體介紹。

在C#中以下修飾符可應用與參數聲明,并且會使得參數按引用傳遞:ref、out、readonly ref、in。對于每個修飾符具體怎么使用就不再這里細說了。

相信到這里你應該就可以回答我之前在《LeetCode題集-2 - 兩數相加》最后提的問題了。

:測試方法代碼以及示例源碼都已經上傳至代碼庫,有興趣的可以看看。https://gitee.com/hugogoos/Planner

轉自https://www.cnblogs.com/hugogoos/p/18419656


該文章在 2025/1/2 8:42:30 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved