`class` 定义范围内的局部变量与 `def` 方法范围

Local variables in `class` definition scope versus `def` method scope

在这里,我在 class 范围内创建了一个局部变量:

class MyClass
  x = 1
  puts x
end

即使我没有创建 MyClass 的任何实例,它也会打印 1

我想在某些方法中使用 x:

class MyClass
  x = 1
  def method
    puts x
  end
end

m = MyClass.new
m.method

我不能。为什么?我知道 class 定义创建了一个范围,但为什么在方法中无法访问它?方法的范围不在 class 的范围内吗?

我可以想象这与创建 class 有关。由于任何class都是Class的对象,所以MyClass的范围可能是某些Class方法的范围,以及MyClass的耦合方法的方式那个实例使它们的范围完全不同。

在我看来,我不能只用 {}(就像在 C 中一样)或 do..end 之类的东西创建一个范围。我说得对吗?

方法的作用域是而不是 class。每个方法都有自己全新的作用域。

只要您使用 classmoduledef 关键字,就会创建新范围。与在 C 中一样,使用方括号不会创建新的作用域,事实上您不能使用方括号任意组合代码行。 Ruby 块周围的方括号(或 do...end)创建块级作用域,其中先前在周围作用域中创建的变量可用,但在块内创建的变量作用域之后不会逃逸到周围的作用域中。

实例方法与其他实例方法共享它们的实例变量的范围。在 class 定义范围内定义的实例变量在 class 级别的单例方法中可用,但在 class.

的实例方法中不可用

插图:

class Foo
  x = 1  # available only here
  @y = 2 # class-wide value

  def self.class_x
    @x # never set; nil value
  end

  def self.class_y
    @y # class-wide value
  end

  def initialize(z)
    x = 3  # available only here
    @z = z # value for this instance only
  end

  def instance_x
    @x # never set; nil
  end

  def instance_y
    @y # never set; nil
  end

  def instance_z
    @z # value for this instance only
  end
end

Foo.class_x # => nil
Foo.class_y # => 2

Foo.new(0).instance_x # => nil
Foo.new(0).instance_y # => nil

foo3 = Foo.new(3)
foo4 = Foo.new(4)

foo3.instance_z # => 3
foo4.instance_z # => 4

您可以使用 class 级 getter 从实例中访问 class 级实例变量。继续上面的例子:

class Foo
  def get_class_y
    self.class.class_y
  end
end

foo = Foo.new(0)
foo.get_class_y # => 2

Ruby 中存在使用 @@ 印记的 "class variable," 概念。实际上,这种语言结构几乎没有合理的用例。通常,使用 class 级实例变量可以更好地实现目标,如此处所示。

Here, I create a local variable in class scope:

class MyClass
  x = 1
  puts x
end

It prints 1 even if I don't create any instances of MyClass.

正确。 class 定义体在读取时执行。它只是像任何其他代码一样的代码,class 定义体没有什么特别之处。

问问自己:attr_reader/attr_writer/attr_accessoralias_methodpublic/protected/private 否则工作?哎呀,如果 def 在定义 class 时没有被执行,它会如何工作? (毕竟,def 和其他表达式一样只是一个表达式!)

这就是为什么你可以做这样的事情:

class FileReader
  if operating_system == :windows
    def blah; end
  else
    def blubb; end
  end
end

I want to use x in some method:

class MyClass
  x = 1
  def method
    puts x
  end
end

m = MyClass.new
m.method

And I can't. Why? I get that class definition creates a scope, but why is it not accessible in the method? Isn't scope of the method inside the scope of the class?

不,不是。 Ruby中有4个作用域:脚本作用域、module/class定义作用域、方法定义作用域和block/lambda作用域。只有 blocks/lambdas 嵌套,所有其他的都创建新的范围。

I can imagine that this is related to creation of a class. Since any class is an object of Class, maybe the scope of MyClass is the scope of some Class method, and the way of coupling methods of MyClass to that instance makes their scope completely different.

老实说,我不完全明白你在说什么,但是不,class定义范围不是方法定义范围,class定义范围是class定义范围,和方法定义范围是方法定义范围。

It also seems to me that I can't just create a scope with {} (like in C) or something like do..end. Am I correct?

就像我上面说的:Ruby 中有 4 个作用域。 C 中没有块作用域。("block" 的 Ruby 概念与 "block." 的 C 概念完全不同)你能得到的最接近的东西是 JavaScript-inspired immediately-invoked lambda-literal,像这样:

foo = 1

-> {
  bar = 2
  foo + bar
}.()
# => 3

bar
# NameError

一般来说,在Ruby中没有必要。在结构良好的代码中,方法会非常小,跟踪局部变量及其作用域和生命周期真的不是什么大问题。

So just creating a class without any instances will lead to something actually executing in runtime (even allocating may be)? That is very not like C++. –

查看此代码:

Dog = Class.new do
  attr_accessor :name

  def initialize(name)
    @name = name
  end
end

如果你执行那段代码,不会有任何输出,但还是会发生一些事情。例如,创建了一个名为 Dog 的全局变量,它有一个值。证明如下:

Dog = Class.new do
  attr_accessor :name

  def initialize(name)
    @name = name
  end
end

dog = Dog.new("Ralph")
puts dog.name

--output:--
Ralph

上面对Dog常量的赋值相当于这样写:

class Dog
  ...
  ...
end

而且,事实上,ruby 遍历 class 定义中的每一行并执行每一行——除非代码行在 def 中。 def 已创建,但 def 中的代码在调用 def 之前不会执行。

您将在 class 定义中看到的一个非常常见的行是:

attr_accessor :name

...可以重写为:

attr_accessor(:name)

...这很明显这是一个方法调用。当您 运行 包含 class 定义的文件时,Ruby 执行该行并调用该方法。 attr_accessor 方法然后 动态创建并插入 一个 getter 和一个 setter 方法到 class 中。在运行时。是的,这不再是 C++ 领域了——欢迎来到 NeverNever Land。

I get that class definition creates a scope, but why is it not accessible in the method?

因为那是 Matz 决定事情应该的方式:def 创建一个新的作用域,阻止变量在 def 之外的可见性。但是,有一些方法可以打开作用域门,可以说:blocks 可以看到周围作用域中定义的变量。查看 define_method():

class MyClass
  x = 1

  define_method(:do_stuff) do
    puts x
  end

end

m = MyClass.new
m.do_stuff

--output:--
1

块是 do...end 之间的所有内容。在ruby中,一个块是一个闭包,这意味着当一个块被创建时,它会捕获周围范围内的变量,并携带这些变量,直到块被执行。块就像一个匿名函数,它作为参数传递给方法。

请注意,如果您使用 Class.new 技巧,您可以打开两个范围门:

x = 1

MyClass = Class.new do

  define_method(:do_stuff) do
    puts x
  end

end

m = MyClass.new
m.do_stuff

--output:--
1

一般来说,ruby 允许程序员做任何他们想做的事,规则是该死的。