Ruby类实例变量与类变量


179

我读了“ 何时设置Ruby实例变量? ”,但是当使用类实例变量时,我有两个想法。

类变量由类的所有对象共享,实例变量属于一个对象。如果我们有类变量,那么使用类实例变量的空间就不多了。

有人可以解释这两者之间的区别以及何时使用它们吗?

这是一个代码示例:

class S
  @@k = 23
  @s = 15
  def self.s
    @s
  end
  def self.k
     @@k
  end

end
p S.s #15
p S.k #23

我现在知道,类实例变量没有沿继承链传递!

Answers:


276

类的实例变量:

class Parent
  @things = []
  def self.things
    @things
  end
  def things
    self.class.things
  end
end

class Child < Parent
  @things = []
end

Parent.things << :car
Child.things  << :doll
mom = Parent.new
dad = Parent.new

p Parent.things #=> [:car]
p Child.things  #=> [:doll]
p mom.things    #=> [:car]
p dad.things    #=> [:car]

类变量:

class Parent
  @@things = []
  def self.things
    @@things
  end
  def things
    @@things
  end
end

class Child < Parent
end

Parent.things << :car
Child.things  << :doll

p Parent.things #=> [:car,:doll]
p Child.things  #=> [:car,:doll]

mom = Parent.new
dad = Parent.new
son1 = Child.new
son2 = Child.new
daughter = Child.new

[ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]

在类上(而不是在该类的实例上)使用实例变量,您可以存储该类共有的内容,而无需让子类自动获得它们(反之亦然)。使用类变量,您不必self.class从实例对象中进行编写便具有便利,并且(在需要时)还可以在整个类层次结构中获得自动共享。


将它们合并为一个示例,该示例还涵盖实例上的实例变量:

class Parent
  @@family_things = []    # Shared between class and subclasses
  @shared_things  = []    # Specific to this class

  def self.family_things
    @@family_things
  end
  def self.shared_things
    @shared_things
  end

  attr_accessor :my_things
  def initialize
    @my_things = []       # Just for me
  end
  def family_things
    self.class.family_things
  end
  def shared_things
    self.class.shared_things
  end
end

class Child < Parent
  @shared_things = []
end

然后采取行动:

mama = Parent.new
papa = Parent.new
joey = Child.new
suzy = Child.new

Parent.family_things << :house
papa.family_things   << :vacuum
mama.shared_things   << :car
papa.shared_things   << :blender
papa.my_things       << :quadcopter
joey.my_things       << :bike
suzy.my_things       << :doll
joey.shared_things   << :puzzle
suzy.shared_things   << :blocks

p Parent.family_things #=> [:house, :vacuum]
p Child.family_things  #=> [:house, :vacuum]
p papa.family_things   #=> [:house, :vacuum]
p mama.family_things   #=> [:house, :vacuum]
p joey.family_things   #=> [:house, :vacuum]
p suzy.family_things   #=> [:house, :vacuum]

p Parent.shared_things #=> [:car, :blender]
p papa.shared_things   #=> [:car, :blender]
p mama.shared_things   #=> [:car, :blender]
p Child.shared_things  #=> [:puzzle, :blocks]  
p joey.shared_things   #=> [:puzzle, :blocks]
p suzy.shared_things   #=> [:puzzle, :blocks]

p papa.my_things       #=> [:quadcopter]
p mama.my_things       #=> []
p joey.my_things       #=> [:bike]
p suzy.my_things       #=> [:doll] 

@Phronz您在代码中提到的self.things和self.class.things有什么区别?
半机械人

1
@cyborg 在当前作用域中self.things引用了一个方法things(如果是类的实例,将是该实例的方法),其中self.class.things引用了things当前作用域的类中的一个方法(同样在类的实例中,这意味着类方法)。
graffzon

美丽的解释。
aliahme922

30

我相信主要的区别(唯一?)是继承:

class T < S
end

p T.k
=> 23

S.k = 24
p T.k
=> 24

p T.s
=> nil

类变量由所有“类实例”(即子类)共享,而类实例变量仅特定于该类。但是,如果您从不打算扩大班级,则区别纯粹是学术上的。


1
这不是唯一的区别。“共享”与“实例”之间的关系不仅仅是继承。如果放置实例获取器S.new.s => nil,则将得到和S.new.k => 23
安德烈·菲盖雷多

27

资源

实例方法的可用性

  • 类实例变量仅适用于类方法,不适用于实例方法。
  • 类变量可用于实例方法和类方法。

可继承性

  • 类实例变量在继承链中丢失。
  • 类变量不是。
class Vars

  @class_ins_var = "class instance variable value"  #class instance variable
  @@class_var = "class variable value" #class  variable

  def self.class_method
    puts @class_ins_var
    puts @@class_var
  end

  def instance_method
    puts @class_ins_var
    puts @@class_var
  end
end

Vars.class_method

puts "see the difference"

obj = Vars.new

obj.instance_method

class VarsChild < Vars


end

VarsChild.class_method

15

就像其他人所说的,类变量在给定的类及其子类之间共享。类实例变量恰好属于一个类。它的子类是单独的。

为什么存在这种现象?好吧,Ruby中的所有东西都是对象,甚至是类。这意味着每个类都有对应于该类的对象Class(或的子类Class)。(当您说时class Foo,您实际上是在声明一个常量Foo并为其分配一个类对象。)每个Ruby对象都可以具有实例变量,因此类对象也可以具有实例变量。

麻烦的是,类对象上的实例变量实际上并不像您通常希望类变量那样运行。您通常希望将超类中定义的类变量与其子类共享,但这不是实例变量的工作方式-子类具有自己的类对象,而该类对象也具有自己的实例变量。因此,他们采用了您更可能想要的行为来引入了单独的类变量。

换句话说,类实例变量有点像Ruby的设计。除非您明确知道它们是您要找的东西,否则您可能不应该使用它们。


所以类变量就像Java中的静态变量一样?
Kick Buttowski

3

官方Ruby常见问题解答:类变量和类实例变量之间有什么区别?

主要区别在于有关继承的行为:类变量在类及其所有子类之间共享,而类实例变量仅属于一个特定的类。

在某种程度上,类变量可以看作是继承层次结构中的全局变量,而所有问题都与全局变量有关。例如,一个类变量可能(偶然地)由其任何子类重新分配,从而影响所有其他类:

class Woof

  @@sound = "woof"

  def self.sound
    @@sound
  end
end

Woof.sound  # => "woof"

class LoudWoof < Woof
  @@sound = "WOOF"
end

LoudWoof.sound  # => "WOOF"
Woof.sound      # => "WOOF" (!)

或者,可以重新打开和更改祖先类,可能会产生令人惊讶的效果:

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

Foo.var  # => "foo" (as expected)

class Object
  @@var = "object"
end

Foo.var  # => "object" (!)

因此,除非您完全知道自己在做什么,并且明确需要这种行为,否则最好使用类实例变量。


2

对于那些具有C ++背景的人,您可能会对与C ++等效语言的比较感兴趣:

class S
{
private: // this is not quite true, in Ruby you can still access these
  static int    k = 23;
  int           s = 15;

public:
  int get_s() { return s; }
  static int get_k() { return k; }

};

std::cerr << S::k() << "\n";

S instance;
std::cerr << instance.s() << "\n";
std::cerr << instance.k() << "\n";

如我们所见,k是一个staticlike变量。这是100%像一个全局变量,不同之处在于它拥有由类(作用域是正确的)。这样可以更轻松地避免名称相似的变量之间发生冲突。像任何全局变量一样,该变量只有一个实例,并且对其进行修改始终对所有人可见。

另一方面,s是对象特定值。每个对象都有其自己的值实例。在C ++中,必须创建一个实例才能访问该变量。在Ruby中,类定义本身就是类的实例(在JavaScript中,这称为原型),因此您可以s从类进行访问而无需其他实例化。可以修改类实例,但是对的修改s将特定于每个实例(每个类型的对象S)。因此,修改一个不会改变另一个的值。


1

尽管利用类实例变量似乎立即有用,但是由于类实例变量在子类之间共享,并且可以在单例和实例方法中引用它们,所以存在一个明显的缺点。它们是共享的,因此子类可以更改类实例变量的值,并且基类也将受到更改的影响,这通常是不受欢迎的行为:

class C
  @@c = 'c'
  def self.c_val
    @@c
  end
end

C.c_val
 => "c" 

class D < C
end

D.instance_eval do 
  def change_c_val
    @@c = 'd'
  end
end
 => :change_c_val 

D.change_c_val
(irb):12: warning: class variable access from toplevel
 => "d" 

C.c_val
 => "d" 

Rails引入了一个方便的方法,称为class_attribute。顾名思义,它声明了一个类级别的属性,其值可被子类继承。可以在单例方法和实例方法中访问class_attribute值,与class instance变量一样。但是,Rails中class_attribute的巨大好处是子类可以更改其自身的值,并且不会影响父类。

class C
  class_attribute :c
  self.c = 'c'
end

 C.c
 => "c" 

class D < C
end

D.c = 'd'
 => "d" 

 C.c
 => "c" 

好电话,我以前没用过。尽管您需要确保self.每次要访问该属性时都在其前面添加前缀c,例如self.c。文档说default:可以将参数传递给,class_attribute但由于我刚才提到的观点,它似乎不起作用self
Dex

当您说“使用类实例变量似乎立即有用”时,我认为您的意思是“类变量”,而不是“类实例变量对吗?”(请参阅ruby-lang.org/en/documentation/faq/8/。
Keith Bennett

是的,这个答案完全混淆了“类实例变量”和“类变量”,这是问题的重点。
stevo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.