Home [C# 筆記] 結構 Struct
Post
Cancel

[C# 筆記] 結構 Struct

Struct 結構體

Struct 結構與 Class 相似卻不一樣,結構體是一種「值類型」的數據類型。

1
2
3
[訪問修飾符] struct 結構體的名字 {
    結構體成員
}

舉例:遊戲結構體

1
2
3
4
5
6
struct Game {
    public string name;
    public string developer;
    public DateTime releaseDate;
}

  • 不需要使用 new創建物件,直接宣告物組完成之後,這個物件就會被同時創建。
  • 由於結構體 struct 是「值類型」物件,所以會在 「Stack(棧/堆疊)」內存中分配相應的內存地址。
1
2
3
4
5
6
7
static void Main(string[] args)
{
    Game game;
    game.name = "Rokomon";
    game.developer = "Rii";
    game.releaseDate = DateTime.Today;
}
  • 一樣可以賦值,除了可以創建成員變量,我們同樣也可創建成員方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Game {
    public string name;
    public string developer;
    public DateTime releaseDate;

    public void GetInfo() {
        Console.WriteLine($"遊戲名稱:{name}");
        Console.WriteLine($"Developer:{developer}");
        Console.WriteLine($"Release Date:{releaseDate}");
    }
}

static void Main(string[] args) {
    Game game;
    game.name = "Rokomon";
    game.developer = "Rii";
    game.releaseDate = DateTime.Today;

    game.GetInfo();

    Console.Read();
}

結構體是 C++ 流傳下來的一種比較特殊的語法結構,而類似Java 和 Python這樣的高階語言已經拋棄這種語法了,而為什麼 C# 依然選擇保留結構體這種數據結構呢?

結構體的使用場景

  • 電腦的內存分為:「棧內存(Stack/堆疊)」和「堆內存(Heap/堆積)」,但是「棧內存(Stack/堆疊)」執行效率卻高於「堆內存(Heap/堆積)」。
  • 「引用類型」保存在「堆內存(Heap/堆積)」中,而「值類型」則保存在「棧內存(Stack/堆疊)」。
  • class 對象是「引用類型」保存在「堆內存(Heap/堆積)」中,執行效率比較低。
  • struct 是「值類型」,保存在「棧內存(Stack/堆疊)」中,運行效率高

我們都知道電腦的內存空間是有限的,它可以被分為:「棧內存(Stack/堆疊)」和「堆內存(Heap/堆積)」,「堆內存(Heap/堆積)」的空間遠大於「棧內存(Stack/堆疊)」,但是「棧內存(Stack/堆疊)」的執行效率卻高於「堆內存(Heap/堆積)」。

「引用類型」保存在「堆內存(Heap/堆積)」中,而「值類型」則保存在「棧內存(Stack/堆疊)」。

一個 Class 對象是「引用類型」保存在「堆內存(Heap/堆積)」中,所以執行效率比較低。

struct 是「值類型」,保存在「棧內存(Stack/堆疊)」中,它的運行效率非常高。

所以對於一個坐標點、一個形狀或者顏色,這樣比較輕量級的物件,我們可以通過使用結構體struct 保存在「棧內存(Stack/堆疊)」中,這樣運行效率會有明顯的提升。

所以使用結構 struct的成本,一般來說比使用 class 低。

class 的使用場景

  • 抽象的概念,或者需要多個層級來表現物件關係
  • 適用於結構複雜的數據

但是,如果我們需要使用到抽象的概念,或者需要多個層級來表現物件關係的時候,類class是我們最好的選擇。

因為結構struct的保存方式註定了它不能支持過於複雜的數據結構,而結構struct也不支持繼承、不支持抽象。

一般來說,如果我們的目標類型只包含數據,或者以數據為主導,那麼使用結構體struct是最佳的選擇,所以這也就是為什麼 C# 依然保留了 struct這種比較高效的數據類型。

結構體的特點

  • 可帶有方法、字段、索引、屬性、運算符方法和事件
  • 結構 struct不能定義無參的默認構結方法
  • 結構可以實現介面,但它不能繼承,也不能被繼承
  • 實體化可以使用 new(),但也可以不用 new()

那麼結構體有哪些特點呢?首先與 class 一樣,它可以帶有方法、字段、索引、屬性、運算符方法和事件,但是,結構 struct不能定義無參的默認構結方法,因為默認的構造方法是結構體預定的,我們無法更改。不過除了無參數的構造方法以外,我們還是可以定義其他的有參數的構造方法的。

結構可以實現介面,但它不能繼承,也不能被繼承,因為無法繼承,所以我們也不會在結構中使用到 objectvirtualprotect 等關鍵詞。

最後我們可以通過使用 new 來創建結構物件,不過不使用 new 同樣也可以創建結構實體。

如果不使用 new 這個操作服務,那麼所有的字段都需要被初始化,被賦值以後才能夠使用。

試驗 Struct 以上的特性

沒有參數的默認構造方法(使用無參構方法會報錯)

可以看到,默認構造方法是報錯的狀態,不過我們可以通過添加參數的方式來重載這個構造方法。

但請一定要注意,構造方法的參數必須包含所有的成員字段,並且在構造方法內部全部初始化。

1
2
3
4
5
6
7
8
9
10
//使用無參構方法雖然會報錯
public Game() { } //會報錯

//不過我們可以通過添加參數的方式來重載這個構造方法
public Game(string name, string developer, DateTime date)
{
    this.name = name;
    this.developer = developer;
    this.releaseDate = date;
}

而我們在使用結構struct的時候,還有一點必須非常小心: 就是我們沒有辦法訪問一個還沒有被初始化的數據。

比如說回到 main方法,第一行我們聲明了一個 game 結構體,這個時候 game 這個物件是不包含任何數據的,所以如果我們在第二行馬上執行game.GetInfo(),這個時候我們會發現編譯器報錯了,這是因為 game 內部的成員還沒有被初始化,我們還不能訪問這些變量。

1
2
3
4
5
6
7
8
9
10
11
12
13
static void Main(string[] args)
{
    Game game; //第一行我們聲明了一個game 結構體, 這個時候game這個物件是不包含任何數據的
    //game.GetInfo(); //報錯。所以如果我們在第二行馬上執行`game.GetInfo()`,這個時候我們會發現編譯器報錯了
    game.name = "Rokomon";
    //game.GetInfo();//報錯
    game.developer = "Rii";
    //game.GetInfo();報錯
    game.releaseDate = DateTime.Today;

    game.GetInfo();
    Console.Read();
}

必須等到這些所有的成員變量全部初始化完成以後,我們才可以進行後續的邏輯操作,所以無論我們把game.GetInfo()放在第二行,還是初始化了 devloper 之後,都不可以執行 game.GetInfo()的操作,必須等待所有的成員變量通通初始化完成之後,game.GetInfo()才可以被使用,這也是structclass 區別。

1
2
3
4
5
6
7
8
9
10
static void Main(string[] args)
{
    Game game; //聲明了一個game 結構體, 這個時候game這個物件是不包含任何數據的
    game.name = "Rokomon";
    game.developer = "Rii";
    game.releaseDate = DateTime.Today;

    game.GetInfo(); //必須等待所有的成員變量通通初始化完成之後,game.GetInfo()才可以被使用
    Console.Read();
}

在使用 class 的時候,即使我們沒有對它進行實體化,我們依舊可以訪問他所有的成員變量和方法,當然在運行過程中,可能會出現系統崩潰的可能性,但在編譯過程中是不會報錯的,所以這也就是從另一個側面反映出使用struct 要比使用 class 更加安全,更加可靠。

最後一點,做為「值類型」,struct是不可以為空的

比如說,現在我們是不可以把 game 賦值為null

1
2
3
4
5
6
7
8
9
10
11
12
static void Main(string[] args)
{
    Game game;
    game.name = "Rokomon";
    game.developer = "Rii";
    game.releaseDate = DateTime.Today;

    game = null;//編譯器馬上就會報錯

    game.GetInfo();
    Console.Read();
}

當我們把一個結構體賦值為 null,編譯器馬上就會報錯。

那麼對於「值類型」空數據的處理…TODO

https://www.bilibili.com/video/BV1Ss4y1B7zE?p=47

This post is licensed under CC BY 4.0 by the author.