关于语法:@@ variable在Ruby中意味着什么?

What does @@variable mean in Ruby?

什么是Ruby变量前面有双符号(@@)? 我对一个带有at符号的变量的理解是它是一个实例变量,在PHP中是这样的:

PHP版本

1
2
3
4
5
6
7
8
9
10
11
12
class Person {

    public $name;

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

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

Ruby相当于

1
2
3
4
5
6
7
8
9
10
class Person

    def set_name(name)
        @name = name
    end

    def get_name()
        @name
    end
end

符号@@的双重含义是什么,它与符号中的单个符号有什么不同?


前缀为@的变量是实例变量,而前缀为@@的变量是类变量。看看下面的例子;它的输出位于puts行末尾的注释中:

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 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类的实例。一个例子:

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
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)以证明这可能不像您期望的那样100%表现;我上面链接的文章有很多关于这个主题的额外信息。

另请注意,与大多数数据一样,您应该在多线程环境中对类变量非常小心,根据dmarkow的评论。


@ - 类的实例变量
@@ - 类变量,在某些情况下也称为静态变量

类变量是在类的所有实例之间共享的变量。这意味着对于从此类实例化的所有对象,只存在一个变量值。如果一个对象实例更改了该变量的值,则该新值将基本上针对所有其他对象实例进行更改。

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


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

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


答案是部分正确的,因为@@实际上是一个类变量,它是每个类层次结构,这意味着它由类,它的实例及其后代类及其实例共享。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

这将输出

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

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

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

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
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是每个类而不是类层次结构,因为它实际上是存储在每个类实例上的变量。这是输出:

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

一个重要的区别是,您无法直接从实例方法访问这些类变量(或您可以说的类实例变量),因为实例方法中的@people将引用该Person或Student或Graduate类的特定实例的实例变量。

因此,虽然其他答案正确地指出@myvariable(使用单个@表示法)始终是实例变量,但它并不一定意味着它不是该类的所有实例的单个共享变量。


当类扩展或包含该模块时,@和@@ in模块的工作方式也不同。

所以给定

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
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

然后,您将下面的输出显示为注释

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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

因此,在模块中使用@@表示您希望它们共同使用的变量,并在模块中使用@,以便为每个使用上下文分别使用变量。