67
class Hello
@hello = "hello"
    def display
        puts @hello
    end
end

h = Hello.new
h.display

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

4

6 回答 6

96

初次学习 Ruby 时,ruby 中的实例变量可能会有些混乱,尤其是当您习惯于其他 OO 语言(如 Java)时。

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

关于 ruby​​ 中的实例变量,除了带有 @ 符号前缀的符号之外,最重要的事情之一是它们在第一次被分配给.

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在类级别分配时创建一个类实例变量。

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

于 2009-05-06T08:00:53.020 回答
45

您需要添加一个initialize方法:

class Hello
    def initialize
        @hello = "hello"
    end
    def display
        puts @hello
    end
end

h = Hello.new
h.display
于 2009-05-05T20:22:24.587 回答
24

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

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

从技术上讲,当您在class范围内时,您self将设置为当前类的对象,并@variables与您当前的self. 男孩,我很讨厌解释这些事情。

通过观看The Pragmatic Programmers 的每个 5 美元的截屏视频集,您可以了解所有这些以及更多信息。

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

于 2009-05-05T20:38:17.340 回答
10

《The ruby​​ Programming Language》一书中有明确的描述,阅读会很有帮助。我把它贴在这里(来自第 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。

于 2010-11-11T06:29:11.033 回答
5

我忘记了 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_variableand@instance_variable总是按照你的预期表现:前者是在类级别定义的,无论是在类方法中还是在实例方法中引用,它都会在顶部保存分配给它的值。后者仅在 class 的对象中获取值T,因此在 class 方法中,它引用了值为 的未知变量nil

想象中命名的类方法按预期class_method输出@@class_variable和 两个@class_instance_variables 的值,即在类的顶部初始化。但是,在实例方法initialize和中instance_method,访问的是同名的不同变量,即实例变量,而不是类实例变量

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

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

于 2014-12-25T19:03:43.237 回答
1

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

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
于 2011-02-20T15:56:15.520 回答