为什么我不能在 Integer class 中覆盖 self?

Why can't I overwrite self in the Integer class?

我希望能够写 number.incr,像这样:

num = 1; num.incr; num #=> 2

我看到的错误是: Can't change the value of self

如果那是真的,那该多好!方法有用吗?

根本不可能将self更改为另一个对象。 self 是消息发送的接收者。只能有一个。

If that's true, how do bang! methods work?

爆炸 (!) 只是方法名称的一部分。它绝对没有任何特殊意义。 Ruby 程序员之间的惯例是用一声巨响命名不那么令人惊讶的方法的令人惊讶的变体,但这只是一个惯例。

您不能更改 self 的值

对象是 class pointer and a set of instance methods(请注意,此 link 是 Ruby 的旧版本,因为它非常简单,因此更便于解释)。

"Pointing" at an object 意味着你有一个变量存储对象在内存中的位置。然后要对对象做任何事情,你首先要到内存中的位置(我们可能会说"follow the pointer")来获取对象,然后做事情(例如调用一个方法,设置一个ivar)。

所有 Ruby 处处的代码都在某个对象的上下文中执行。这是你的实例变量被保存的地方,它是 Ruby 寻找没有接收者的方法的地方(例如 $stdout$stdout.puts "hi" 中的接收者,当前对象是接收者在 puts "hi")。有时您需要对当前对象做一些事情。使用对象的方法是通过变量,但是什么变量指向当前对象呢?没有一个。为满足这一需要,提供了关键字 self

self 就像一个变量,因为它指向当前对象的位置。但它不像一个变量,因为你不能给它赋新的值。如果可以的话,那一点之后的代码会突然在不同的对象上运行,这很令人困惑,并且与仅使用变量相比没有任何好处。

还要记住,对象由存储内存地址的变量跟踪。 self = 2 是什么意思?它是否仅意味着当前代码的运行就像它被调用一样2?或者这是否意味着所有指向旧对象的变量现在都更新了它们的值以指向新对象?这不是很清楚,但前者不必要地引入了身份危机,而后者的成本高得令人望而却步,并引入了不清楚什么是正确的情况(我将在下面详细介绍)。

你不能改变 Fixnums

Some objects 在 Ruby 中的 C 级别是特殊的(false、true、nil、fixnums 和 symbols)。

指向它们的变量实际上并不存储内存位置。相反,地址本身存储对象的类型和标识。无论什么重要的地方,Ruby 都会检查它是否是一个特殊对象(例如,当 looking up an instance variable 时),然后从中提取值。

所以内存中没有存储对象 123 的位置。这意味着 self 包含 Fixnum 123 的概念,而不是像往常一样的内存地址。与变量一样,它会在必要时进行特殊检查和处理。

因此,您不能改变对象本身(尽管它们似乎保留 a special global variable 以允许您在 Symbols 等对象上设置实例变量)。

他们为什么要做这一切?我假设是为了提高性能。存储在寄存器中的数字只是一系列位(通常为 32 位或 64 位),这意味着有加法和乘法等硬件指令。也就是说,ALU 被连接起来在一个时钟周期内执行这些操作,而不是用软件编写算法,这会花费很多数量级的时间。通过这样存储它们,他们避免了在内存中存储和查找对象的成本,并且他们获得了可以使用硬件直接将两个指针相加的优势。但是请注意,在 Ruby 中仍然存在一些 C 中没有的额外成本(例如检查溢出并将结果转换为 Bignum)。

爆炸方法

您可以在任何方法的末尾放置一个感叹号。它不需要改变对象,只是人们通常会在你做一些可能会产生意想不到的副作用的事情时警告你。

class C
  def initialize(val)
    @val = val         # => 12
  end                  # => :initialize

  def bang_method!
    "My val is: #{@val}"  # => "My val is: 12"
  end                     # => :bang_method!
end                       # => :bang_method!

c = C.new 12    # => #<C:0x007fdac48a7428 @val=12>
c.bang_method!  # => "My val is: 12"
c               # => #<C:0x007fdac48a7428 @val=12>

此外,整数没有 bang 方法,它不符合范例

Fixnum.instance_methods.grep(/!$/)  # => [:!]

# Okay, there's one, but it's actually a boolean negation
1.!  # => false

# And it's not a Fixnum method, it's an inherited boolean operator
1.method(:!).owner  # => BasicObject

# In really, you call it this way, the interpreter translates it
!1  # => false

备选方案

  • 创建一个包装器对象:我不打算提倡这个,但它最接近您想要做的事情。基本上创建你自己的 class,它是可变的,然后让它看起来像一个整数。有一个很棒的博客 post 在 http://blog.rubybestpractices.com/posts/rklemme/019-Complete_Numeric_Class.html 浏览了这篇文章,它会让你完成 95% 的过程
  • 不要直接依赖于 Fixnum 的值:如果不知道您正在尝试做什么/为什么您觉得有必要,我无法提供比这更好的建议。

另外,当你问这样的问题时,你应该出示你的代码。很长一段时间我都误解了你是怎么接近它的。