【问题标题】:When do Ruby instance variables get set?Ruby 实例变量何时设置?
【发布时间】:2010-10-24 00:39:11
【问题描述】:
class Hello
@hello = "hello"
    def display
        puts @hello
    end
end

h = Hello.new
h.display

我创建了上面的类。它不会打印任何东西。我认为实例变量@hello 是在类声明期间设置的。但是当我调用 display 方法时,输出是'nil'。这样做的正确方法是什么?

【问题讨论】:

    标签: ruby syntax instance-variables


    【解决方案1】:

    我忘记了 Ruby 中有“类实例变量”的概念。无论如何,OP的问题似乎令人费解,并且迄今为止在任何答案中都没有真正解决,除了kch的答案中的提示:这是一个范围问题。 (在编辑时添加:实际上,sris 的答案确实在最后解决了这一点,但无论如何我都会让这个答案站稳脚跟,因为我认为示例代码可能有助于理解问题。)

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

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

    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
    

    我根据我的想法命名变量,但事实并非总是如此:

    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_variables 的值,即在类顶部初始化。但是,在实例方法initializeinstance_method中,访问的是不同变量同名,即实例变量,而不是类实例变量

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

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

    【讨论】:

      【解决方案2】:

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

      在类定义内部但在类定义外部使用的实例变量 实例方法定义是一个类实例变量

      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
      

      ......

      因为类实例变量只是类的实例变量 对象,我们可以使用 attr、attr_reader 和 attr_accessor 来创建 它们的访问器方法。

      class << self
        attr_accessor :n, :totalX, :totalY
      end
      

      定义这些访问器后,我们可以将原始数据称为 Point.n、Point.totalX 和 Point.totalY。

      【讨论】:

        【解决方案3】:

        第一次学习 Ruby 时,ruby 中的实例变量可能会有点令人困惑,尤其是当您习惯于另一种 OO 语言(如 Java)时。

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

        关于 ruby​​ 中的实例变量,除了带有 @ 符号前缀的表示法之外,最重要的一点是 它们在第一次被分配时就开始活跃。 p>

        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 列出对象的所有实例变量。

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

        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 时创建一个类实例变量。

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

        【讨论】:

        • 您说“他们第一次被分配到生命中”,但 OP 显示了一个比您的示例更早的(明显)分配示例,并且遇到的问题是所述变量没有从而生机勃勃。
        • @kaleidic 没错。我对这个没有解决 OP 问题的答案的赞成票数量感到有点困惑。事实上,类实例变量@hello确实在示例代码的第 2 行开始出现,但问题是这不是第 4 行引用的变量。有关详细信息,请参阅下面的答案。
        • 对不起,你确实在最后回答了这个问题,我在第一次阅读时错过了这个问题。
        • 我的输出是[:@hello] 怎么样?
        【解决方案4】:

        我还建议查看以“@@”为前缀的类变量 - 这里有一些示例代码向您展示类和实例变量的不同之处:

        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
        

        【讨论】:

          【解决方案5】:

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

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

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

          您可以通过观看this collection of $5-each screencasts from The Pragmatic Programmers 了解所有这些以及更多信息。

          (或者您可以在这里要求澄清,我会尝试更新。)

          【讨论】:

          • 一个很好的article 详细阐述了类级实例变量。
          【解决方案6】:

          你需要添加一个initialize方法:

          class Hello
              def initialize
                  @hello = "hello"
              end
              def display
                  puts @hello
              end
          end
          
          h = Hello.new
          h.display
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-03-01
            • 2014-07-08
            • 2018-07-18
            • 1970-01-01
            • 1970-01-01
            • 2012-01-07
            • 2014-09-21
            • 1970-01-01
            相关资源
            最近更新 更多