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不能定義無參的默認構結方法,因為默認的構造方法是結構體預定的,我們無法更改。不過除了無參數的構造方法以外,我們還是可以定義其他的有參數的構造方法的。
結構可以實現介面,但它不能繼承,也不能被繼承,因為無法繼承,所以我們也不會在結構中使用到 object、virtual 與 protect 等關鍵詞。
最後我們可以通過使用 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()才可以被使用,這也是struct 和 class 區別。
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