结构体¶
您无需使用 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
的副本,其中包含对数组的引用。 object.array << "foo"
修改了array
引用的数组(在其中添加了元素)。- 这也反映在原始的
strukt
中,因为它持有对同一数组的引用。 object.array = ["new"]
用对新数组的引用替换了strukt
副本中的引用。object.array << "bar"
附加到这个新创建的数组。modify
返回对这个新数组的引用,并打印其内容。- 对这个新数组的引用仅在
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