結構體¶
除了使用 class
定義型別,您也可以使用 struct
struct Point
property x, y
def initialize(@x : Int32, @y : Int32)
end
end
結構體繼承自 Value,因此它們會在堆疊上分配,並以傳值方式傳遞:當傳遞給方法、從方法回傳或賦值給變數時,實際上會傳遞值的副本(而類別繼承自 Reference,會在堆積上分配,並以傳參考方式傳遞)。
因此,結構體主要用於不可變的資料型別和/或其它型別的無狀態包裝器,通常是為了效能考量,以避免在傳遞小型副本可能更有效率時,進行大量小型記憶體配置(更多細節請參閱效能指南)。
仍然允許使用可變的結構體,但如果您想避免以下描述的意外情況,在撰寫涉及可變性的程式碼時應謹慎。
傳值¶
結構體總是以傳值方式傳遞,即使您從該結構體的方法中回傳 self
也是如此
struct Counter
def initialize(@count : Int32)
end
def plus
@count += 1
self
end
end
counter = Counter.new(0)
counter.plus.plus # => Counter(@count=2)
puts counter # => Counter(@count=1)
請注意,plus
的鏈式呼叫會回傳預期的結果,但只有第一次呼叫會修改變數 counter
,因為第二次呼叫是在第一次呼叫傳遞給它的結構體副本上運作,而這個副本在表達式執行後會被丟棄。
在結構體內部處理可變型別時也應謹慎
class Klass
property array = ["str"]
end
struct Strukt
property array = ["str"]
end
def modify(object)
object.array << "foo"
object.array = ["new"]
object.array << "bar"
end
klass = Klass.new
puts modify(klass) # => ["new", "bar"]
puts klass.array # => ["new", "bar"]
strukt = Strukt.new
puts modify(strukt) # => ["new", "bar"]
puts strukt.array # => ["str", "foo"]
這裡的 strukt
會發生什麼事
Array
是以傳參考方式傳遞,因此["str"]
的參考會儲存在strukt
的屬性中- 當
strukt
傳遞給modify
時,會傳遞strukt
的副本,其中包含陣列的參考 - 由
array
參考的陣列會被修改(在其內部加入元素),透過object.array << "foo"
- 這也會反映在原始的
strukt
中,因為它持有同一個陣列的參考 object.array = ["new"]
會用新陣列的參考取代strukt
的副本中的參考object.array << "bar"
會附加到這個新建立的陣列modify
會回傳對這個新陣列的參考,並印出其內容- 對這個新陣列的參考僅保留在
strukt
的副本中,而不是原始的strukt
中,因此原始的strukt
只保留了第一個敘述的結果,而不是其他兩個敘述的結果
Klass
是一個類別,因此它會以傳參考方式傳遞給 modify
,而 object.array = ["new"]
會將新建立的陣列的參考儲存在原始的 klass
物件中,而不是像 strukt
那樣儲存在副本中。
繼承¶
第二點是有原因的:結構體具有非常明確的記憶體佈局。例如,上述 Point
結構體佔用 8 個位元組。如果您有一個點的陣列,則點會嵌入在陣列的緩衝區內
# The array's buffer will have 8 bytes dedicated to each Point
ary = [] of Point
如果繼承了 Point
,則此類型的陣列也應考慮到其內部可能存在其他類型,因此每個元素的大小應增加以容納該類型。這絕對是意料之外的。因此,不能繼承非抽象結構體。另一方面,抽象結構體會有後代,因此預期它們的陣列會考慮到其內部可能有多種類型。
結構體還可以包含模組,並且可以像類別一樣是泛型的。
記錄¶
Crystal 標準程式庫提供了 record
巨集。它可以簡化基本結構體類型的定義,並包含初始化器和一些輔助方法。
record Point, x : Int32, y : Int32
Point.new 1, 2 # => #<Point(@x=1, @y=2)>
record
巨集會展開為以下的結構體定義
struct Point
getter x : Int32
getter y : Int32
def initialize(@x : Int32, @y : Int32)
end
def copy_with(x _x = @x, y _y = @y)
self.class.new(_x, _y)
end
def clone
self.class.new(@x.clone, @y.clone)
end
end