URLhttps://learnscript.net/zh-hant/programming/data/structures/
    複製連結移至說明  範例

    結構介紹,結構與類別的區別

    閱讀 6:47·字數 2039·發佈 
    Youtube 頻道
    訂閱 375

    先決條件

    閱讀本節的先決條件是對實值型別,參考型別,堆疊,堆積等概念有所掌握,你可以檢視堆疊,堆積,指標介紹一節來了解他們。

    需要說明,本節的「類別₁」一詞,表示與結構無關的類別,如果語言將所有資料型別均視為類別的話。

    結構

    結構(Structure)也被稱為結構體,他會通過一系列成員來儲存資料,這看上去和類別₁有點相似,但類別₁通常更註重功能,而結構則以資料為重點,即便在某些語言中,結構可以擁有自己的方法。

    另外,結構是實值型別,而類別₁通常是參考型別,雖然結構本身可能也是類別的一種,但特定的約束將使其具有確定的儲存空間,以符合實值型別的要求。

    為何使用結構?

    想必結構的設計初衷與類別是相同的,沒有人願意面對重複而枯燥的程式碼,在不影響可讀性的情況下,少定義一些變數,何樂而不為哪?從淩亂走向簡潔,沒有人會拒絕。

    結構的成員

    如前所述,結構以資料為重點,因此,欄位是他的標準成員,如果你將實值型別和參考型別的不同,視為結構和類別₁的本質區別,那麽結構擁有類別₁的其他特性將是可以理解的。比如在 C# 中,從程式碼上看,結構和類別₁的界線已經越來越模糊了,結構的成員已經拓展到了屬性,方法,建構子。

    下面的 C# 結構MyDate擁有三個欄位YearMonthDay,以及一個建構子。

    structures.cs
    // 結構 MyDate,表示日期
    struct MyDate
    {
    	// 欄位 Year,表示年份
    	public int Year;
    	// 欄位 Month,表示月份
    	public int Month;
    	// 欄位 Day,表示一月中的第幾天
    	public int Day;
    
    // 建構子 public MyDate(int year, int month, int day) { // 初始化結構的欄位 Year = year; Month = month; Day = day; } }

    結構的成員存取層級

    對於結構的某些成員,比如欄位,其存取層級應該預設為公用,原因很簡單,既然側重於資料,那麽定義的欄位多半是提供給外部存取的。當然,是否預設為公用,語言會有自己的決定。

    結構和執行效率

    實值型別和預設的傳值,可能會使采用結構的邏輯執行效率降低,當結構包含大量資料並且程式碼中存在頻繁的指派行為時,因為這將導致需要進行反覆且代價不菲的結構建立。

    如何解決結構可能帶來的效率問題?

    這裏有兩種可能的方案,將傳值改為傳址,或者,將結構改為類別₁。傳址可以減少結構的建立次數,本應被建立的複本會被本體取代,這種方案需要語言給予支援。改為類別₁只能是嘗試性的,因為類別₁的建立同樣是耗時的,如果不能從邏輯上大量減少其建立次數,那麽你將不會從使用類別₁中受益。

    無論采用哪種方案,請確保原本需要實作的業務目標不會受到影響。當然,檢查設計也是必要的,或許結構本身就不應該包含如此眾多的資料。

    建立結構MyDate的執行個體後,嘗試通過先指派再設定的方式修改月份,發現結果顯示的月份依然為1,這是因為指派產生了新的MyDate執行個體。

    *.cs
    // 建立結構 MyDate 的執行個體
    MyDate date = new(2024, 1, 16);
    
    // 嘗試增加月份 for (int i = 0; i < 5; i++) { // 由於結構是實值型別,預設的傳值將產生一個新的結構執行個體 MyDate newDate = date; newDate.Month++; }
    Console.WriteLine($"日期為:{date.Year}-{date.Month}-{date.Day}");
    日期為:2024-1-16

    將定義變數newDate的陳述式改為傳址的方式後,再次執行程式,月份顯示為6

    *.cs
    // 采用傳址不會產生新的執行個體,修改月份得到了滿意的效果
    ref MyDate newDate = ref date;
    日期為:2024-6-16

    結構的參考型別成員

    雖然結構是實值型別,但其成員卻可以是參考型別,當該成員儲存某個類別₁的執行個體時。事實上,該措施是必須的,因為你不可能將空間大小不確定的目標放入堆疊中,取而代之的只能是對該目標的參考。當然,這意味著結構執行個體和他的複本可能會用到某個類別₁的同一個執行個體。

    下面,為結構MyDate增加一個參考型別的欄位Todo

    structures.cs
    // 結構 MyDate,表示日期
    struct MyDate
    {
    	// …
    	// 欄位 Todo,表示當天需要完成的事情
    	public TODO Todo = new();
    	// …
    }
    
    // 類別 TODO,表示需要完成的事情 class TODO { // 屬性 Content,表示事情的內容 public string Content { get; set; } = string.Empty; }

    建立MyDate的執行個體,並通過指派的方式產生一個該執行個體的複本,通過dateTodoTodo欄位修改TODO執行個體的Content屬性,發現dateRest也受到了影響,因為他們使用了同一個TODO執行個體。

    *.cs
    // 建立 MyDate 的執行個體 dateRest
    MyDate dateRest = new(2000, 1, 16);
    // 修改 Todo 表示的 TODO 執行個體的 Content 屬性
    dateRest.Todo.Content = "沒什麽事情";
    
    // 建立 MyDate 的執行個體 dateTodo MyDate dateTodo = dateRest; // 修改年份 dateTodo.Year = 2024; // 修改 Todo 表示的 TODO 執行個體的 Content 屬性 dateTodo.Todo.Content = "有事了";
    Console.WriteLine($"{dateRest.Year}-{dateRest.Month}-{dateRest.Day}{dateRest.Todo.Content}"); Console.WriteLine($"{dateTodo.Year}-{dateTodo.Month}-{dateTodo.Day}{dateTodo.Todo.Content}");
    2000-1-16:有事了
    2024-1-16:有事了

    原始碼

    structures.cs·codebeatme/programming·GitHub