【问题标题】:Why can't I overwrite self in the Integer class?为什么我不能在 Integer 类中覆盖 self ?
【发布时间】:2015-03-20 22:37:03
【问题描述】:

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

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

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

如果那是真的,那么砰!方法有效吗?

【问题讨论】:

  • bang 方法(在其他类上)通常会改变对象的状态,而不是改变对象本身。 Fixnum 没有状态,所以...

标签: ruby class metaprogramming


【解决方案1】:

你不能改变 self 的值

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

“指向”一个对象意味着您有一个变量,该变量将对象的位置存储在内存中。然后对对象做任何事情,你首先去内存中的位置(我们可以说“跟随指针”)来获取对象,然后做事情(例如调用一个方法,设置一个ivar)。

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

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

还要记住,对象由存储内存地址的变量跟踪。 self = 2 应该是什么意思?这是否仅仅意味着当前代码的运行就像调用了2 一样?或者这是否意味着所有指向旧对象的变量现在都更新了它们的值以指向新对象?目前还不是很清楚,但前者不必要地引入了身份危机,而后者则过于昂贵,并引入了不清楚什么是正确的情况(我将在下面进一步讨论)。

你不能改变 Fixnums

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

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

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

因此,您无法改变对象本身(尽管它们似乎保留了a special global variable 以允许您在符号之类的东西上设置实例变量)。

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

爆炸方法

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

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

替代方案

  • 制作一个包装对象:我不打算提倡这个,但它最接近您想要做的。基本上创建你自己的类,它是可变的,然后让它看起来像一个整数。 http://blog.rubybestpractices.com/posts/rklemme/019-Complete_Numeric_Class.html 有一篇很棒的博文介绍了它,它会让你走完 95% 的路
  • 不要直接依赖于 Fixnum 的值:在不知道您要做什么/为什么您觉得这是必要的情况下,我无法提供比这更好的建议。

此外,当您提出此类问题时,您应该展示您的代码。我误解了你很长一段时间是如何接近它的。

【讨论】:

    【解决方案2】:

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

    如果那是真的,那么砰!方法有效吗?

    bang (!) 只是方法名称的一部分。它绝对没有任何特殊含义。 Ruby 程序员之间的约定是用爆炸来命名不太出人意料的方法的出人意料的变体,但这就是:约定。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-30
      相关资源
      最近更新 更多