关于语法:什么时候设置Ruby实例变量?

When do Ruby instance variables get set?

1
2
3
4
5
6
7
8
9
class Hello
@hello ="hello"
    def display
        puts @hello
    end
end

h = Hello.new
h.display

我创建了上面的类。它什么都不打印出来。我以为实例变量@hello是在类声明期间设置的。但当我调用display方法时,输出为"nil"。正确的方法是什么?


Ruby中的实例变量在首次学习Ruby时可能会有点混乱,特别是如果您习惯了像Java这样的另一种OO语言。

不能简单地声明实例变量。

对于Ruby中的实例变量,除了带有@符号前缀的符号外,最重要的一点是它们在第一次被分配时就开始活跃起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Hello
  def create_some_state
    @hello ="hello"
  end
end

h = Hello.new
p h.instance_variables

h.create_some_state
p h.instance_variables

# Output
[]
["@hello"]

可以使用方法Object#instance_variables列出对象的所有实例变量。

您通常"声明"并初始化初始化方法中的所有实例变量。明确记录哪些实例变量应该公开的另一种方法是使用模块方法attr_accessor(读/写)、attr_writer(写)和attr_reader(读)。这些方法将为列出的实例变量合成不同的访问器方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Hello
  attr_accessor :hello
end

h = Hello.new
p h.instance_variables

h.hello ="hello"
p h.instance_variables

# Output
[]
["@hello"]

直到使用合成的Hello#hello=方法将实例变量分配给它,实例变量仍然不会被创建。

另一个重要的问题,如KCH所描述的,是在声明类时需要注意活动的不同上下文。声明类时,最外层作用域中的默认接收器(self)将是表示类本身的对象。因此,当在类级别上分配给@hello时,代码将首先创建一个类实例变量。

在方法内部,self将是调用方法的对象,因此您试图打印对象中不存在名为@hello的实例变量的值(请注意,读取一个不存在的实例变量是完全合法的)。


您需要添加一个initialize方法:

1
2
3
4
5
6
7
8
9
10
11
class Hello
    def initialize
        @hello ="hello"
    end
    def display
        puts @hello
    end
end

h = Hello.new
h.display


代码中的第一个@hello称为类实例变量。

它是类对象的一个实例变量,常量Hello指向它。(这是Class类的一个实例。)

从技术上讲,当您在Class范围内时,您的self被设置为当前类的对象,而@variables则属于当前的self。伙计,我不擅长解释这些事情。

你可以通过观看这个5美元的集合,从务实的程序员那里得到所有这些和更多的澄清。

(或者你可以在这里要求澄清,我会尽量更新。)


在《Ruby编程语言》一书中有一个清晰的描述,阅读它会很有帮助。我把它贴在这里(摘自第7.1.16章):

An instance variable used inside a class definition but outside an
instance method definition is a class instance variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Point
    # Initialize our class instance variables in the class definition itself
    @n = 0              # How many points have been created
    @totalX = 0         # The sum of all X coordinates
    @totalY = 0         # The sum of all Y coordinates

    def initialize(x,y) # Initialize method
      @x,@y = x, y      # Sets initial values for instance variables
    end

    def self.new(x,y)   # Class method to create new Point objects
      # Use the class instance variables in this class method to collect data
      @n += 1           # Keep track of how many Points have been created
      @totalX += x      # Add these coordinates to the totals
      @totalY += y

      super             # Invoke the real definition of new to create a Point
                    # More about super later in the chapter
    end

    # A class method to report the data we collected
    def self.report
        # Here we use the class instance variables in a class method
        puts"Number of points created: #@n"
        puts"Average X coordinate: #{@totalX.to_f/@n}"
        puts"Average Y coordinate: #{@totalY.to_f/@n}"
    end
end

……

Because class instance variables are just instance variables of class
objects, we can use attr, attr_reader, and attr_accessor to create
accessor methods for them.

1
2
3
class << self
  attr_accessor :n, :totalX, :totalY
end

定义了这些访问器后,我们可以将原始数据称为point.n、point.totalx和point.totaly。


我忘记了Ruby中有一个"类实例变量"概念。无论如何,OP的问题似乎令人费解,之前的任何答案都没有真正解决,除了KCH答案中的一个提示:这是一个范围问题。(在编辑部分增加:事实上,SRIS的答案确实在最后解决了这一点,但我还是让这个答案成立,因为我认为示例代码可能有助于理解问题。)

在Ruby类中,以@开头的变量名可以引用两个变量中的一个:实例变量或类实例变量,具体取决于引用的类中的位置。这是一个相当微妙的发现。

一个例子将阐明这一点。下面是一个小Ruby测试类(所有代码都在IRB中测试):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class T

  @@class_variable ="BBQ"
  @class_instance_variable_1 ="WTF"
  @class_instance_variable_2 ="LOL"

  def self.class_method
    puts"@@class_variable           == #{@@class_variable           || 'nil'}"
    puts"@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
    puts"@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
    puts"@instance_variable         == #{@instance_variable         || 'nil'}"
  end

  def initialize
    @instance_variable ="omg"
    # The following line does not assign a value to the class instance variable,
    # but actually declares an instance variable withthe same name!
    @class_instance_variable_1 ="wtf"
    puts"@@class_variable           == #{@@class_variable           || 'nil'}"
    # The following two lines do not refer to the class instance variables,
    # but to the instance variables with the same names.
    puts"@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
    puts"@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
    puts"@instance_variable         == #{@instance_variable         || 'nil'}"
  end

  def instance_method
    puts"@@class_variable           == #{@@class_variable           || 'nil'}"
    # The following two lines do not refer to the class instance variables,
    # but to the instance variables with the same names.
    puts"@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
    puts"@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
    puts"@instance_variable         == #{@instance_variable         || 'nil'}"
  end

end

我根据自己的想法来命名变量,尽管事实并非总是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
irb> T.class_method
@@class_variable           == BBQ
@class_instance_variable_1 == WTF    # the value of the class instance variable
@class_instance_variable_2 == LOL    # the value of the class instance variable
@instance_variable         == nil    # does not exist in the class scope
=> nil

irb> t = T.new
@@class_variable           == BBQ
@class_instance_variable_1 == wtf    # the value of the instance variable
@class_instance_variable_2 == nil    # the value of the instance variable
@instance_variable         == omg
=> #<T:0x000000015059f0 @instance_variable="omg", @class_instance_variable_1="wtf">

irb> t.instance_method
@@class_variable           == BBQ
@class_instance_variable_1 == wtf    # the value of the instance variable
@class_instance_variable_2 == nil    # the value of the instance variable
@instance_variable         == omg
=> nil

irb> T.class_method
@@class_variable           == BBQ
@class_instance_variable_1 == WTF    # the value of the class instance variable
@class_instance_variable_2 == LOL    # the value of the class instance variable
@instance_variable         == nil    # does not exist in the class scope
=> nil

@@class_variable@instance_variable的行为总是如您所期望的那样:前者是在类级别上定义的,无论是在类方法中引用还是在实例方法中引用,它都在顶部保存分配给它的值。后者只在类T的对象中获取一个值,因此在类方法中,它指的是一个值为nil的未知变量。

类方法想象地命名为class_method,按预期输出@@class_variable和两个@class_instance_variable的值,即在类的顶部初始化的值。但是,在实例方法initializeinstance_method中,可以访问相同名称的不同变量,即实例变量,而不是类实例变量。

可以看到,initialize方法中的赋值没有影响类实例变量@class_instance_variable_1,因为后面的class_method调用输出它的旧值"WTF"。相反,方法initialize声明了一个新的实例变量,该变量也被命名为(误导地)@class_instance_variable_1。分配给它的值,"WTF"通过方法initializeinstance_method输出。

示例代码中的变量@class_instance_variable_2相当于原始问题中的变量@hello:它被声明并初始化为类实例变量,但当实例方法引用该名称的变量时,它实际上看到了一个同名的实例变量--一个从未声明过的变量,因此其值为零。


我还建议查看前缀为"@@"的类变量-下面是一些示例代码,向您展示类和实例变量的不同之处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Vars
  @@classvar="foo"
  def test
    @instancevar="bar"
  end
  def Vars.show
    puts"classvar: #{@@classvar}"
    puts"instancevar: #{@instancevar}"
  end
  def instance_show
    puts"classvar: #{@@classvar}"
    puts"instancevar: #{@instancevar}"

  end
end

# only shows classvar since we don't have an instance created
Vars::show
# create a class instance
vars = Vars.new
# instancevar still doesn't show b/c it hasn't been initialized
vars.instance_show
# initialize instancevar
vars.test
# now instancevar shows up as we expect
vars.instance_show