在 ruby 中引用 class 实例变量时理解 self 关键字?

Understanding the self keyword when referring to a class instance variable in a ruby?

我正在尝试理解 ruby 对象和 self 关键字。

假设我有一个 class:

class CashRegister
  attr_accessor :total

  def initialize(total)
    @total = total
  end

  def add(amount)
    self.total = self.total + amount
  end
end

cr = CashRegister.new(5)
puts cr.total #=> 5
cr.add(10)
puts cr.total #=> 15

假设我在上面的添加方法中删除了 self 关键字:

def add(amount)
   total = total + amount
end

我得到一个错误:

cr = CashRegister.new(5)
puts cr.total #=> 5
cr.add(10) #=> this throws an error: ./lib/test.rb:28:in `add': undefined method `+' for nil:NilClass (NoMethodError)

我假设这是因为我需要 self 关键字来引用实例变量 total

假设我有另一个类似的 class:

class School
  attr_accessor :name, :roster

  def initialize(name)
    @name = name
    @roster = {}
  end

  def add_student(student, grade)
    roster[grade] = roster[grade] || []
    roster[grade] << student
  end
end

school = School.new("Jefferson High")
puts school.roster #=> {}
school.add_student("Sam", 10)
puts school.roster #=> {10=>["Sam"]} 

为什么我在 add_student 中不需要自己?

tl;dr:foo = value 将始终引用局部变量 foo 而不是方法调用 self.foo=(value).


I am assumed this is because I need the self keyword to refer to the instance variable, total

不是,total是局部变量,不是实例变量。 @total 是一个实例变量。局部变量存在于当前范围内,就像单个方法调用一样。实例变量与对象保持一致。

您需要 self 关键字来引用方法 total=。让我们开始吧。

attr_accessor :total 声明了两个方法,totaltotal=。这些是获取和设置实例变量 @total 的包装器。以下代码执行等效操作。

def total
  @total
end

def total=(value)
  @total = value
end

注意这个方法的名字是total=,这个稍后会变得很重要。

考虑到这一点,让我们看看您的代码。

def add(amount)
  self.total = self.total + amount
end

(几乎)Ruby 中的所有内容实际上都是方法调用。以上是在 self.

上调用 total= 方法的语法糖
def add(amount)
  self.total=(self.total + amount)
end

现在如果我们像这样删除 self 会发生什么?

def add(amount)
  total = total + amount
end

在Ruby中,self是可选的。 Ruby 会判断 total 是指方法 total 还是局部变量 total局部变量优先并且赋值总是给局部变量.

total = total + amount 像这样工作:

def add(amount)
  total = self.total + amount
end

始终赋值给局部变量。

为了进一步说明,如果我们先声明 total 会怎样?

def add(amount)
  total = 23
  self.total = total + amount
end

现有局部变量 total 优先于 total() 方法。 total + amount 指的是局部变量 total 所以 cr.add(10); puts cr.total 将是 33.


total = ... 将始终引用局部变量 total。出于这个原因,如果你想使用方法赋值,你必须显式地使用 self.total=。在其他情况下,您可以删除 self。并避免与方法同名的局部变量。

def add(amount)
  # self.total = self.total + amount
  self.total = total + amount
end

Why did I not need self in add_student?

因为没有歧义。您没有分配给局部变量 roster.

def add_student(student, grade)
  roster[grade] = roster[grade] || []
  roster[grade] << student
end

roster[grade]真的是self.roster.[]=(grade)。您正在调用 self.roster which returns a Hash 然后调用该哈希上的 []= 方法来给它一个新的 key/value 对。

类似地,roster[grade] << studentself.roster.[](grade).<<(student). Get aHash, call [[]](https://ruby-doc.org/core/Hash.html#method-i-5B-5D) on it to retrieve theArrayand call [<<](https://ruby-doc.org/core/Array.html#method-i-3C-3C) on thatArray`.

def add_student(student, grade)
  self.roster.[]=(grade) = self.roster.[](grade) || []
  self.roster.[](grade).<<(student)
end

是的,[][]=<< 都是方法名称。


一旦理解语法糖下的方法调用以及 [][]=<< 等内容,许多 Ruby 的谜团就会消失是方法名称。