Learning hard C#學習筆記——讀書筆記 07

来源:https://www.cnblogs.com/trueasureyuki/archive/2023/07/19/17565457.html
-Advertisement-
Play Games

本文介紹了值類型和引用類型在編程中的區別。值類型包括簡單類型、枚舉類型和結構體類型,通常被分配線上程的堆棧上,變數保存的是實例數據本身。引用類型實例則被分配在托管堆上,變數保存的是實例數據的記憶體地址。值類型由操作系統負責管理,而引用類型則由垃圾回收機制(GC)負責管理。本文還通過示例代碼展示了值類型... ...



1.值類型和引用類型

1.1 什麼是值類型和引用類型

  • 值類型:包括簡單類型,枚舉類型,結構體類型等,值類型通常被分配線上程的堆棧上,變數保存的內容就是實例數據本身
  • 引用類型:引用類型實例則被分配在托管堆上,變數保存的是實例數據的記憶體地址,引用類型主要包括類類型、介面類型、委托類型、字元串類型等




1.2 值類型和引用類型的區別


值類型和引用類型最主要的區別是——不同的記憶體分佈

我們之前介紹過,值類型分配在線程的堆棧上,引用類型分配在托管堆上,不同的分配位置導致了不同的管理機制,值類型由操作系統負責管理,引用類型則由垃圾回收機制(GC)負責管理


管理的主要是記憶體的分配與釋放

class Program {
    static void Main(string[] args) {
        // valuetype 是值類型
        int valuetype = 3;
        // reftype 是引用類型
        string reftype = "abc";
    }
}

在程式中,每個變數都有其堆棧地址,並且不同的變數,堆棧地址不同,valuetype 和 reftype 在堆棧地址占用了不同的位置,從下圖可以看出,無論是值類型還是引用類型,變數本身都是存儲在堆棧中,變數只是實例數據的一個引用





值類型的變數和實際數據通常會存儲線上程堆棧中,而引用類型則是,變數存儲線上程堆棧中,而實際數據存儲在托管堆中,此時變數存儲的是實際數據的地址,這個地址就像是我們實際生活中的地址,像快遞員,想要送包裹給你,也是需要你的地址

註意:我們對值類型的說法是通常線上程堆棧中,而也有不在堆棧的情況


1.引用類型嵌套值類型

如果類的欄位類型是值類型,它作為引用類型的一部分,會被分配到托管堆中,但是局部變數的值類型,則仍會被分配到線程棧中

public class NestedValueTypeInRef {
    // 這個和引用類型一起分配到托管堆中
    private int valueType = 3;

    public method() {
        // 方法的局部變數分配到線程棧中
        char c = 'c';
    }
}

class Program {
    static void Main(string[] agrs) {
        NestedRefTypeInValue reftype = new NestedRefTypeInValue();
    }
}


2.值類型嵌套定義引用類型


值類型嵌套定義引用類型,堆棧上將保存該引用類型的引用,而實際的數據則將保存在托管堆上

public class TestClass 
{
    public int x;
    public int y;
}

public struct NestedRefTypeValue 
{
    // 註意結構體的欄位不能初始化
    private TestClass classinValueType;
  
    // 註意結構體的構造函數不能無參
    public NestedRefTypeValue(TestClass t)
    {
        classinValueType.x = 3;
        classinValueType.y = 5;
        classinValueType = t;
    }
}

class Program {
    static void Main(string[] args) {
        NestedRefTypeValue valueType = new NestedRefTypeInValue(new TestClass())
    }
}



總結:

  1. 值類型繼承自ValueType,ValueType又繼承自System.Object;而引用類型則直接繼承自System.Object
  2. 值類型的記憶體不受GC控制,作用域結束,值類型會被系統自動釋放,從而減少了托管堆的壓力,而引用類型受到GC控制,所以與引用類型相比,值類型性能方面更占優勢
  3. 值類型是密封的(sealed),你不能把值類型作為其它任何類型的基類,而引用類型一般具有繼承性
  4. 值類型不能為null值,它的預設初始值為數值0,而由於類型在預設情況下為null值
  5. 由於值類型的變數包含其實際數據,因此在預設情況下,值類型之間的參數傳遞不會影響變數本身,而引用類型保存的數據的地址,它們作為參數傳遞,參數會發生變化,從而影響引用類型變數的值


1.3 兩大類型轉換——裝箱與拆箱

由於C#存在這兩種類型,自然需要對它們進行轉換,類型轉換指的是將數據的類型轉化為另外一種類型


類型轉換的方式

  1. 隱式類型轉換:由低級類型向高級類型轉換的過程。例如:派生類可以隱式的轉換為它的父類,裝箱的過程就屬於指針隱式類型轉換
  2. 顯式類型轉換:強制類型轉換。這種轉換可能會導致精度丟失,或出現運行異常

強制類型轉換的格式

type就是你想要轉換的類型

(type)(變數、或函數)
  1. 通過 isas 運算符可以進行安全的類型轉換
if (myObj is MyClass)
{
    // myObj 是 MyClass 類型的實例
}

MyClass myObj = someObj as MyClass;
if (myObj != null)
{
    // someObj 成功轉換為 MyClass 類型
}
  1. 通過 .NET 類庫中的Convert類來完成類型轉換
string str = "123";
int num = Convert.ToInt32(str);
string str = "2023-07-19";
DateTime date = Convert.ToDateTime(str);
string str = "Red";
Color color = (Color)Enum.Parse(typeof(Color), str);

1.3.1 裝箱和拆箱的原理

class Program {
    static void Main(string[] args) {
        int i = 3;
        // 裝箱操作
        object o = i;
        // 拆箱操作
        int y = (int) o;
    }
}

裝箱(box)可以具體為三個步驟

  1. 記憶體分配:在托管堆中分配好記憶體空間存放複製的實際數據
  2. 完成數據的複製:將值類型實例的實際數據複製到新分配的記憶體中
  3. 地址返回:將托管對象的地址返回給由於類型變數



拆箱(unbox)操作的步驟:

  1. 檢查實例:首先檢查要進行拆箱操作的引用類型變數是否為null,如果為null則拋出異常,如果不為null,則繼續檢查變數是否和拆箱之後的類型是否是同一個類型
  2. 地址返回:返回已裝箱變數的實際數據部分的地址
  3. 數據複製:將托管堆中的實際數據複製到棧中


註意

  1. 裝箱和拆箱堆性能有比較大的影響,如果代碼中有大量的裝箱和拆箱會消耗很多運行時間
  2. 裝箱和拆箱必然會產生多餘的對象,進一步加重了GC的壓力

應該避免多次的裝箱和拆箱,最好使用泛型來編程

2. 參數傳遞的問題


C# 中的參數傳遞,我們可以分為四種情況

  1. 值類型參數按值傳遞
  2. 引用類型的參數按值傳遞
  3. 值類型參數按引用傳遞
  4. 引用類型的參數按引用傳遞

2.1 值類型的參數按值傳遞

參數可以分為實參和形參兩種,形參指的是被調用方法中的參數,而實參指的是我們傳遞過去的參數

class Program {
    static void Main(string[] args) {
        int addNum = 1;
        // addNum 就是實參
        Add(addNum);
    }
    // addnum就是形參,即被調用方法中的參數
    private static void Add(int addnum) {
        addnum += 1;
        Console.WriteLine(addnum);
    }
}

值類型按值傳遞,其實傳遞的是該值類型實例的一個副本,也就是說,方法對參數的操作,並不會影響實際的參數


class Program {
    static void Main(string[] args) {
        int addNum = 1;
        // addNum 就是實參
        Add(addNum);

        Console.WriteLine("調用方法之後的實際參數的值:"+addNum);
        Console.Read();
    }
    // addnum就是形參,即被調用方法中的參數
    private static void Add(int addnum) {
        addnum += 1;
        Console.WriteLine("方法中形參的值:"+addnum);
    }
}



我們可以看到圖中,並沒有一根線將 addNum 與 addnum 進行綁定,addnum使用只是 addNum 的副本



2.2 引用類型按值傳遞


當傳遞的是引用類型的時,傳遞和操作的目標時指向對象的地址,而傳遞的實際內容對地址的複製,由於地址指向的是實際參數的值,當方法對地址進行操作的時候,實際上操作的地址所指向的實際值,當調用方法之後,實參也會被修改


public class RefClass
{
    public int addNum;
}

class Program {
    static void Main(string[] args) {
        Console.WriteLine("引用類型按值傳遞的情況");
        RefClass refClass = new RefClass();
  
        refClass.addNum = 1;
        AddRef(refClass);

        Console.WriteLine("調用方法之後,實際參數的值:"+refClass.addNum);
        Console.Read();
    }

    private static void AddRef(RefClass addnumRef) {
        addNumRef.addNum += 1;
        Console.WriteLine("方法中的addNum值:"+addNumRef.addNum);
    }

}



2.3 string引用類型參數按值傳遞的特殊情況


雖然string類型也是引用類型,但是它按值傳遞,傳遞的參數斌不會因為方法中的形參改變而修改

這個特殊情況是因為string具有不變性,一旦string類型被賦值之後,則它就是不可改變的,即不能通過代碼修改它

2.4 值類型和引用類型的按引用傳遞


不管是值類型還是引用類型,都可以使用 ref 或 out 關鍵字來實現參數按引用傳遞,並且按引用進行傳遞的時候,方法的定義和調用都必須是顯式的使用 ref 或 out 關鍵字,不可省略

按引用傳遞時,不管是值類型,還是引用類型,本質都一樣是告訴編譯器,方法傳遞的是參數地址,而非參數本身

class Program 
{
    static void Main(string[] args) 
    {
        // num 作為實際參數
        int num = 1;
        // refStr 是引用類型實參
        string refStr = "Old string";
  
        // 值類型按引用傳遞
        ChangeByValue(ref num);
        Console.WriteLine(num);
  
        // 引用類型按引用傳遞
        ChangeByRef(ref refStr);
        Console.WriteLine(refStr);

        Console.Read();
  
    }

    private static void ChangeByValue(ref int numValue) 
    {
        numValue = 10;
        Console.WriteLine(numValue);
    }

    private static void ChangeByRef(ref string numRef) 
    {
        numRef = "new string";
        Console.WriteLine(numRef);
    }
}




您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # Spring6 初始 @[toc] ## 每博一文案: ```tex 人生的態度是:抱有最大的希望。 盡最大的努力,做最壞的打算。 —————— 柏拉圖《理想國》 ``` ## 1. 初始 Spring6 閱讀以下代碼: ```java package com.powernode.oa.cont ...
  • 這篇文章主要記錄了本次遇到的問題:即maven在面對不同版本的jar包在pom文件中同時聲明會存在載入覆蓋的問題,於是通過查詢網上相關資料對maven包的載入規則介紹,並通過實際場景對其進行分析驗證 ...
  • # 1.模塊 ``` import 模塊名 ``` ## 1.1 模塊就是程式 任何python程式都可以作為模塊導入,並標明程式(模塊)的位置 ``` import sys sys.path.append('路徑') ``` ``` import hello // 在同一文件夾下 ``` 會在該文 ...
  • [toc] # 引入 - Microsoft.EntityFrameworkCore - Microsoft.EntityFrameworkCore.Design - Microsoft.EntityFrameworkCore.SqlServer - Microsoft.EntityFramewor ...
  • # 一、日誌記錄 日誌記錄是什麼?簡單而言,就是通過一些方式記錄應用程式運行中的某一時刻的狀態,保留應用程式當時的信息。這對於我們進行應用程式的分析、審計以及維護有很大的作用。 作為程式員,我們恐怕誰也不敢保證我們開發的軟體應用一定不存在BUG,一定不會出現故障,而當故障出現的時候,日誌就是我們排查 ...
  • # 基於Avalonia 11.0.0+ReactiveUI 的跨平臺項目開發2-功能開發 ![image-20230718225201652](https://www.raokun.top/upload/2023/07/image-20230718225201652.png) **項目簡介**:目 ...
  • 在使用DevExpress的GridView的時候,我們為了方便,往往使用一些擴展函數,動態創建GridView列的編輯控制項對象,然後我們可以靈活的對內容進行編輯或者使用一些彈出的對話框窗體進行處理內容的錄入,本篇隨筆就是介紹這一主題:在DevExpress的GridView的列中,動態創建列的時候... ...
  • ## 簡介 ##### IoC Ioc控制反轉,是一種設計模式和原則,旨在解耦組件之間的依賴關係,並將對象的創建和管理委托外部容器。是面向編程中一種重要的概念,用於提高代碼的可維護性. 核心思想:通過將控制權從高層轉移到底層模塊,實現對依賴關係的控制反轉,傳統上,一個對象通常負責自己的依賴項創建和管 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...