我读了https://stackoverflow.com/questions/826734/when-do-ruby-instance-variables-get-set,但当使用类实例变量时,我犹豫不决。

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

谁能解释一下这两者的区别以及什么时候使用它们?

下面是一个代码示例:

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

更新:我现在明白了!类实例变量不会沿着继承链传递。


当前回答

正如其他人所说,类变量在给定的类及其子类之间共享。类实例变量只属于一个类;它的子类是分开的。

为什么会有这种行为?Ruby中的所有东西都是对象——甚至是类。这意味着每个类都有一个class类的对象(或者更确切地说,class的子类)对应于它。(当你说类Foo时,你实际上是在声明一个常量Foo并将一个类对象赋值给它。)每个Ruby对象都可以有实例变量,所以类对象也可以有实例变量。

问题是,类对象上的实例变量的行为并不像您通常希望的类变量的行为那样。通常,您希望在超类中定义的类变量与其子类共享,但实例变量不是这样工作的——子类有自己的类对象,而类对象有自己的实例变量。所以他们引入了独立的类变量来区分你更想要的行为。

换句话说,类实例变量在某种程度上是Ruby设计的一个意外。你可能不应该使用它们,除非你明确知道它们是你要找的东西。

其他回答

我认为主要的(唯一的)不同是继承:

class T < S
end

p T.k
=> 23

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

p T.s
=> nil

类变量由所有“类实例”(即子类)共享,而类实例变量仅特定于该类。但如果你从来没有打算扩展你的课程,区别就纯粹是学术上的。

类的实例变量:

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] 

实例方法的可用性

类实例变量仅对类方法可用,对实例方法不可用。 类变量对实例方法和类方法都可用。

继承性

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

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

对于那些有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是一个静态变量。这完全像一个全局变量,除了它由类拥有(范围是正确的)。这样可以更容易地避免名称相似的变量之间的冲突。像任何全局变量一样,该变量只有一个实例,对它的修改总是对所有人可见。

On the other hand, s is an object specific value. Each object has its own instance of the value. In C++, you must create an instance to have access to that variable. In Ruby, the class definition is itself an instance of the class (in JavaScript, this is called a prototype), therefore you can access s from the class without additional instantiation. The class instance can be modified, but modification of s is going to be specific to each instance (each object of type S). So modifying one will not change the value in another.

简单示例显示

类变量的继承性 类实例变量的封装

注意:使用类<< self是一种方便,而不是必须在这个块中的所有方法前面加上self。注意:类<< self修改self,因此它指向父类的元类(参见https://stackoverflow.com/a/38041660/960184)

示例代码

class Parent
  class << self
    attr_reader :class_instance_var

    def class_instance_var=(value)
      @class_instance_var="set by #{self.name} to #{value}"
    end

    def class_var
      @@class_var
    end

    def class_var=(value)
      @@class_var = "set by #{self.name} to #{value}"
    end
  end
end

class Child < Parent
end

# use the instance separately in parent and subclass
puts "\n* Exercising class instance variable setters
* Setting Parent and Child class instance variables differently
* Parent.class_instance_var = 1000\n* Child.class_instance_var = 2000\n\n"

Parent.class_instance_var = 1000
Child.class_instance_var = 2000
puts "Parent.class_instance_var=(#{Parent.class_instance_var})"
puts "Child.class_instance_var=(#{Child.class_instance_var})"

# set class variable in via parent (changes both in parent and subclass)
puts "\n* Exercising Parent class variable setter
* Set class variable value to 3000 using parent, it changes in Child also
* Parent.class_var = 3000\n\n"

Parent.class_var = 3000
puts "Parent.class_var=(#{Parent.class_var})"
puts "Child.class_var=(#{Child.class_var})"

# set class variable in via subclass (changes both in parent and subclass)
puts "\n* Exercising Child class variable setter
* Set class variable value to 5000 using child, it changes in Parent also
* Child.class_var = 5000\n\n"

Child.class_var = 5000
puts "Parent.class_var=(#{Parent.class_var})"
puts "Child.class_var=(#{Child.class_var})"

使用ruby v3.0.2输出

* Exercising class instance variable setters
* Setting Parent and Child class instance variables differently
* Parent.class_instance_var = 1000
* Child.class_instance_var = 2000

Parent.class_instance_var=(set by Parent to 1000)
Child.class_instance_var=(set by Child to 2000)

* Exercising Parent class variable setter
* Set class variable value to 3000 using parent, it changes in Child also
* Parent.class_var = 3000

Parent.class_var=(set by Parent to 3000)
Child.class_var=(set by Parent to 3000)

* Exercising Child class variable setter
* Set class variable value to 5000 using child, it changes in Parent also
* Child.class_var = 5000

Parent.class_var=(set by Child to 5000)
Child.class_var=(set by Child to 5000)