什么时候在 Ruby 中使用 Struct 而不是 Hash?

我没有太多的编程经验,但是,对我来说,Strut 看起来有点像 Hash。

  • 斯特拉特能做好什么?
  • 还有什么是斯特拉特能做,而哈希做不到的吗?

在谷歌之后,结构的概念在 C 语言中很重要,但是我对 C 语言知之甚少。

20735 次浏览

来自 斯特拉特文档:

结构是一种方便的方法,可以使用访问器方法将许多属性捆绑在一起,而不必编写显式类。

另一方面,大麻:

Hash 是键-值对的集合。它类似于 Array,只不过索引是通过任何对象类型的任意键完成的,而不是整数索引。通过键或值遍历哈希的顺序可能看起来是任意的,而且通常不是插入顺序。

主要区别在于如何访问数据。

ruby-1.9.1-p378 > Point = Struct.new(:x, :y)
=> Point
ruby-1.9.1-p378 > p = Point.new(4,5)
=> #<struct Point x=4, y=5>
ruby-1.9.1-p378 > p.x
=> 4
ruby-1.9.1-p378 > p.y
=> 5
ruby-1.9.1-p378 > p = {:x => 4, :y => 5}
=> {:x=>4, :y=>5}
ruby-1.9.1-p378 > p.x
NoMethodError: undefined method `x' for {:x=>4, :y=>5}:Hash
from (irb):7
from /Users/mr/.rvm/rubies/ruby-1.9.1-p378/bin/irb:17:in `<main>'
ruby-1.9.1-p378 > p[:x]
=> 4
ruby-1.9.1-p378 > p[:y]
=> 5

简而言之,如果需要一个 “普通的旧数据”结构类(可以选择使用更多的方法来扩展它) ,那么可以创建一个新的 Struct,如果根本不需要正式类型,那么可以使用 Hash。

结构与使用散列映射有以下不同(除了代码的外观之外) :

  • 结构具有一组固定的属性,同时向散列中添加新键。
  • 调用结构的实例上不存在的属性将导致 NoMethodError,而从散列中获取不存在的键的值将仅返回 null。
  • 即使不同结构的两个实例具有相同的属性并且具有相同的值(即 Struct.new(:x).new(42) == Struct.new(:x).new(42)为 false,而 Foo = Struct.new(:x); Foo.new(42)==Foo.new(42)为 true) ,这两个不同结构的实例也永远不会相等。
  • Structs 的 to_a方法返回一个值数组,而散列表中的 to_a返回一个键-值对数组(其中“成对”表示“两个元素数组”)
  • 如果 Foo = Struct.new(:x, :y, :z),您可以执行 Foo.new(1,2,3)来创建 Foo的实例,而不必拼写属性名称。

因此,要回答这个问题: 当您希望使用一组已知属性对对象建模时,请使用 structs。当你想为任意使用的散列表建模时(例如,计算每个单词在字符串中出现的频率,或者将昵称映射到全名等等) ,这肯定不是结构的工作,而为一个有名字、年龄和地址的人建模则非常适合 Person = Struct.new(name, age, address))。

作为旁注: C 结构与 ruby 结构几乎没有任何关系,所以不要因此而感到困惑。

我知道这个问题几乎得到了很好的回答,但令人惊讶的是,没有人谈到 Struct最大的区别和真正的好处之一。我想这就是为什么 还有人在问

我理解其中的区别,但是当一个 Hash 可以做同样的事情,并且处理起来更简单时,在 Hash 上使用一个 Struct 的真正好处是什么呢?看起来结构是多余的。

Struct再快点

require 'benchmark'


Benchmark.bm 10 do |bench|
bench.report "Hash: " do
50_000_000.times do { name: "John Smith", age: 45 } end
end


bench.report "Struct: " do
klass = Struct.new(:name, :age)
50_000_000.times do klass.new("John Smith", 45) end
end


end


# ruby 2.2.2p95 (2015-04-13 revision 50295) [x64-mingw32].
#                 user     system      total        real
# Hash:       22.340000   0.016000  22.356000 ( 24.260674)
# Struct:     12.979000   0.000000  12.979000 ( 14.095455)


# ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin11.0]
#
#                  user     system      total        real
# Hash:       31.980000   0.060000  32.040000 ( 32.039914)
# Struct:     16.880000   0.010000  16.890000 ( 16.886061)

另一个主要区别是您可以将行为方法添加到结构中。

 Customer = Struct.new(:name, :address) do


def greeting; "Hello #{name}!" ; end


end


Customer.new("Dave", "123 Main").greeting  # => "Hello Dave!"

如果只是封装数据,那么使用 Hash (或者 Hash 数组)就可以了。如果您计划对数据进行操作或与其他数据进行交互,那么一个结构可以打开一些有趣的可能性:

Point = Struct.new(:x, :y)
point_a = Point.new(0,0)
point_b = Point.new(2,3)


class Point
def distance_to another_point
Math.sqrt((self.x - another_point.x)**2 + (self.y - another_point.y)**2)
end
end


puts point_a.distance_to point_b