Ruby中的@@variable是什么意思?

191

双下划线变量(@@)是什么?我理解带有@符号的变量是实例变量,就像PHP中的这个:

PHP版本

class Person {

    public $name;

    public function setName($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

Ruby的等价代码

class Person

    def set_name(name)
        @name = name
    end

    def get_name()
        @name
    end
end

双艾特符号@@代表什么意思,它与单艾特符号有何不同?


115
我不知道,但我感觉它一直在盯着我看。现在我有点害怕用Ruby编程了... - corsiKa
4
TL;DR:对于公众而言,99次中会有98次我会使用“类实例”变量(即在self方法内部使用@),而不是类变量(即@@)。请查看下面的答案,了解其中的原因。 - WattsInABox
5个回答

268

@为前缀的变量是实例变量,而以@@为前缀的变量是类变量。请查看以下示例;输出结果在puts行末的注释中:

class Test
  @@shared = 1

  def value
    @@shared
  end

  def value=(value)
    @@shared = value
  end
end

class AnotherTest < Test; end

t = Test.new
puts "t.value is #{t.value}" # 1
t.value = 2
puts "t.value is #{t.value}" # 2

x = Test.new
puts "x.value is #{x.value}" # 2

a = AnotherTest.new
puts "a.value is #{a.value}" # 2
a.value = 3
puts "a.value is #{a.value}" # 3
puts "t.value is #{t.value}" # 3
puts "x.value is #{x.value}" # 3

你可以看到@@shared在这些类之间是共享的;在一个实例中设置值将更改该类和子类中所有其他实例的值,而一个名为@shared(只有一个@)的变量则不会如此。 [更新] 正如Phrogz在评论中提到的那样,在Ruby中,使用一个实例变量在类本身上来跟踪类级别的数据是一种常见的习惯用法。这可能是一个棘手的主题,有很多额外的阅读材料关于这个主题,但是把它看作是修改Class类,但仅限于你正在使用的Class类的实例即可。例如:
class Polygon
  class << self
    attr_accessor :sides
  end
end

class Triangle < Polygon
  @sides = 3
end

class Rectangle < Polygon
  @sides = 4
end

class Square < Rectangle
end

class Hexagon < Polygon
  @sides = 6
end

puts "Triangle.sides:  #{Triangle.sides.inspect}"  # 3
puts "Rectangle.sides: #{Rectangle.sides.inspect}" # 4
puts "Square.sides:    #{Square.sides.inspect}"    # nil
puts "Hexagon.sides:   #{Hexagon.sides.inspect}"   # 6

我在这里包含了Square示例(它会输出nil),以展示它可能不会像您期望的那样工作。我提供的链接中还有关于这个主题的很多额外信息。另外,请记住,在大多数数据中一样,您应该在多线程环境下非常小心处理类变量,正如dmarkow的评论所述。

2
在我看来,如果您包含代码显示如何在类级别上使用实例变量来跟踪类级数据而不会出现在子类间共享数据的“奇怪”行为,那么这个答案就非常完美了。 - Phrogz
3
我想提醒一下,类变量在多线程环境(如Rails)中可能会有危险/不可靠的情况。 - Dylan Markow
嗯...从某种程度上来说,它听起来像是 PHP 中的静态变量,但继承部分不同。我不认为 PHP 有完全像这样的东西。 - Andrew
即使您不使用多线程,您基本上假定只会有一个东西存在,而这种假设可能永远无法成立。 - Andrew Grimm
5
我不理解ruby class << self end这个代码块的作用,尤其是"<<"运算符。 - davidtingsu
1
如果有人对 class << self 感到困惑,请参考这里 - kapad

47

@ - 类的实例变量
@@ - 类变量,有时也称为静态变量

类变量是在类的所有实例之间共享的变量。这意味着对于从该类实例化的所有对象,只存在一个变量值。如果一个对象实例更改了该变量的值,那么该新值将对所有其他对象实例产生影响。

另一种思考类变量的方式是将其视为单个类上下文中的全局变量。 使用两个 @ 字符(@@)作为变量名前缀来声明类变量。类变量必须在创建时初始化。


感谢。清晰的解释。 - Frank Guo

13

@@ 表示一个类变量,即它可以被继承。

这意味着如果你创建该类的子类,它将会继承这个变量。因此,如果你有一个名为 Vehicle 的类,其中包含类变量 @@number_of_wheels,那么如果你创建一个 class Car < Vehicle ,那么它也将拥有类变量@@number_of_wheels


这意味着,如果您创建该类的子类,则它将继承该变量。因此,如果您有一个名为Vehicle的类,其中包含类变量@@number_of_wheels,那么如果您创建一个class Car < Vehicle,那么它也将具有类变量@@number_of_wheels - Fareesh Vijayarangam
14
如果我有一个名为class Vehicle的类,其中有一个实例变量@number_of_wheels,那么class Car < Vehicle也将有一个名为@number_of_wheels的实例变量。与类变量的主要区别在于,这些类具有“相同”的变量,例如更改一个变量会更改其他变量。 - Michelle Tilley

2
答案部分正确,因为@@实际上是一个类变量,每个类层次结构都有一个,这意味着它由一个类、它的实例和其子类及其实例共享。
class Person
  @@people = []

  def initialize
    @@people << self
  end

  def self.people
    @@people
  end
end

class Student < Person
end

class Graduate < Student
end

Person.new
Student.new

puts Graduate.people

这将会输出:

这将会输出

#<Person:0x007fa70fa24870>
#<Student:0x007fa70fa24848>

因此,Person、Student和Graduate类只有一个相同的@@变量,所有这些类和实例方法都引用相同的变量。

还有一种定义类变量的方法是在类对象上定义(记住每个类实际上是某个东西的实例,它实际上是Class类,但这是另一回事)。您使用@符号而不是@@符号,但无法从实例方法中访问这些变量。您需要有类方法包装器。

class Person

  def initialize
    self.class.add_person self
  end

  def self.people
    @people
  end

  def self.add_person instance
    @people ||= []
    @people << instance
  end
end

class Student < Person
end

class Graduate < Student
end

Person.new
Person.new
Student.new
Student.new
Graduate.new
Graduate.new

puts Student.people.join(",")
puts Person.people.join(",")
puts Graduate.people.join(",")

在这里,@people是每个类的单一值而不是类层次结构,因为它实际上是存储在每个类实例上的变量。这是输出:

#<Student:0x007f8e9d2267e8>,#<Student:0x007f8e9d21ff38>
#<Person:0x007f8e9d226158>,#<Person:0x007f8e9d226608>
#<Graduate:0x007f8e9d21fec0>,#<Graduate:0x007f8e9d21fdf8> 

重要的一点区别是,你不能直接从实例方法中访问这些类变量(或者可以说是类实例变量),因为在实例方法中 @people 会指代该 Person 或 Student 或 Graduate 类的特定实例的实例变量。因此,虽然其他答案正确地说明了只使用单个 @ 表示的 @myvariable 总是实例变量,但这并不一定意味着它不是该类所有实例共享的单个变量。

并不完全正确。@myvariable从来不是“该类所有实例的单个共享变量”。它只是一个实例变量,仅适用于该对象的范围。如果该对象没有为该变量提供访问器,则其他对象(即使它们是声明变量的类的实例)也无法访问它。这种情况与访问属于任何其他对象的实例变量的能力实际上没有区别。 - Huliax

1
"

@和@@在模块中也有不同的工作方式,当一个类继承或包含该模块时。

因此,考虑到

"
module A
    @a = 'module'
    @@a = 'module'

    def get1
        @a          
    end     

    def get2
        @@a         
    end     

    def set1(a) 
        @a = a      
    end     

    def set2(a) 
        @@a = a     
    end     

    def self.set1(a)
        @a = a      
    end     

    def self.set2(a)
        @@a = a     
    end     
end 

然后你会得到下面的输出,显示为注释。
class X
    extend A

    puts get1.inspect # nil
    puts get2.inspect # "module"

    @a = 'class' 
    @@a = 'class' 

    puts get1.inspect # "class"
    puts get2.inspect # "module"

    set1('set')
    set2('set')

    puts get1.inspect # "set" 
    puts get2.inspect # "set" 

    A.set1('sset')
    A.set2('sset')

    puts get1.inspect # "set" 
    puts get2.inspect # "sset"
end 

class Y
    include A

    def doit
        puts get1.inspect # nil
        puts get2.inspect # "module"

        @a = 'class'
        @@a = 'class'

        puts get1.inspect # "class"
        puts get2.inspect # "class"

        set1('set')
        set2('set')

        puts get1.inspect # "set"
        puts get2.inspect # "set"

        A.set1('sset')
        A.set2('sset')

        puts get1.inspect # "set"
        puts get2.inspect # "sset"
    end
end

Y.new.doit

在模块中,对于想要在所有用途中共享的变量,请使用@@,对于想要在每个用途上分开的变量,请使用@。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接