因此, Ruby的实例变量通常在方法中定义类声明——当在方法里声明实例变量时,该实例变量实际上属于该方法所在类的实例,而不是属于该方法。
看下面的程序:
- class Apple
- # 定义第一个方法
- def info1
- # 输出实例变量@a
- puts @a
- end
- # 定义第二个方法
- def info2
- # 为实例变量赋值
- @a = "Hello";
- end
- end
- # 创建Apple类实例
- apple = Apple.new
- # 调用方法
- apple.info2
- apple.info1
从上面的方法中不难看出,虽然定义第一个方法info1时,该方法内输出的@a实例变量还没有被定义,但这不是问题!因为Ruby的方法只有在被调用时才会真正生效,所以当调用apple.info2方法时,执行了@a = "Hello"代码,这行代码动态地为apple实例增加了一个@a实例变量。
注意:Java创建对象时一次为该对象的所有实例变量都分配了相应的内存空间;但Ruby语言里的对象完全是动态的,创建对象时该对象没有任何实例变量,直到执行到为实例变量定义时,该对象才动态增加该实例变量。
类似于全局变量,实例变量无须显式声明即可使用。如果使用一个未定义的实例变量,则该实例变量的值为nil。
与局部变量不同是的:实例变量的生存范围是与该对象的生存范围相同的,只要该类的对象存在,则该对象里的实例变量将一直存在;但局部变量则随方法的消亡而消亡(除非使用闭包)。
实例变量的访问范围总是private,这意味着在类定义内对实例变量的赋值和读取没有限制;如果希望在类外部访问到实例变量的值,则可以通过方法来访问。看如下代码:
- class Apple
- # initialize是一个特殊方法,其实就是一个构造器,在创建Apple实例时自动调用
- def initialize(name)
- # 当执行构造器时添加一个实例变量
- @name=name
- end
- # 定义一个与name实例变量同名的方法,该方法返回实例变量@name
- def name
- return @name
- end
- # 定义一个名为name=的方法,该方法用于为@name实例变量赋值
- def name=(att)
- @name=att
- end
- end
- # 创建Apple的实例
- a = Apple.new("红富士")
- # 通过name方法来访问@name实例变量
- puts a.name
- # 通过调用name=方法来为@name实例变量设置值
- a.name = "青苹果"
- puts a.name
执行上面的代码,看到如下执行结果:
红富士
青苹果
从上面的代码中不难看出,Ruby语言里的实例变量可以被定义成一个属性。实际上,Ruby为定义类里的属性提供了更简单的方法:通过Module的方法定义属性。
这种用法并不是Ruby语法的一部分,它只是通过使用Module类里几个方法来自动创建属性。
看如下代码:
- class Apple
- # 使用attr创建了一个@name实例变量,且为该变量创建了读取方法
- attr :name
- # 使用attr创建了一个@color实例变量,且为该变量创建了读取和设值方法
- attr :color ,true
- # 使用attr_reader创建了一个@weight实例变量,且为该变量创建了读取方法
- attr_reader :weight
- # 使用attr_ writer创建了一个@size实例变量,且为该变量创建了设值方法
- attr_writer :size
- # 使用attr_accessor创建了一个@info实例变量,且为该变量创建了读取和设值方法
- attr_accessor :info
- end
- a = Apple.new
- # @name属性只可访问
- puts a.name
- # @color属性既可设值,也可访问
- a.color = "红色"
- puts a.color
- # @weight属性只可访问
- puts a.weight
- # @size属性只可设值
- a.size = 0.2;
- # @info属性既可设值,也可访问
- a.info = "可口的水果"
- puts a.info
上面的代码的执行结果如下:
nil
红色
nil
可口的水果
从上面的代码中不难看出,通过Module里的4个方法attr,attr_reader,attr_writer和attr_accessor来创建实例变量及其对应的访问方法更加简单。
实际上,上面的Apple类相当于如下代码。
- class Apple
- # 定义name的访问方法
- def name
- return @name
- end
- # 定义color的访问方法
- def color
- return @color
- end
- # 定义color的设值方法
- def color=(att)
- @color = att
- end
- # 定义weight的访问方法
- def weight
- return @name
- end
- # 定义size的设值方法
- def size=(att)
- @size = att
- end
- # 定义info的访问方法
- def info
- return @info
- end
- # 定义info的设值方法
- def info=(att)
- @info = att
- end
- end
除此之外,我们也可以在模块中定义实例变量,在模块中的定义变量的目的并不是用于创建模块的实例(模块是无法创建实例的),而是用于将该实例变量混入其他类中。
在如下代码中,我们在模块里定义了一个实例变量,并将该实例变量混入其他类中。
- # 定义一个Vegetable模块
- module Vegetable
- //在模块里使用attr定义一个@color实例变量
- attr :color ,true
- end
- # 定义一个Apple类
- class Apple
- # 混入Vegetable的实例变量
- include Vegetable
- end
- a = Apple.new
- a.color = "红色"
- puts a.color
从上面的程序中可以看出,Apple类获得了Vegetable模块中的@color实例变量,这就是在模块中定义实例变量的作用。
从上面的代码中不难看出,虽然实例变量是属于类的,但大部分时候我们都是在该类的方法里定义实例变量,而不是在类中定义实例变量。
在类范围内定义的实例变量属于类对象(Class对象,当我们定义一个类时,该类定义会返回一个Class对象,该对象会保存到一个与该类同名的常量里),类范围内定义的实例变量是Class对象(对象名就是该类的类名)的实例变量,实例变量永远都不能暴露成为public访问控制,只能通过方法暴露,因此类范围的实例变量反而没有太大的意义。
- class Apple
- # 定义一个类范围的实例变量
- @color = "红色"
- def info
- # 方法内无法访问类范围的实例变量
- puts @color
- end
- # 类内才可以访问类范围的实例变量
- puts "类范围的实例变量 " + @color
- end
- a = Apple.new
- a.info
上面的代码的执行结果如下:
类范围的实例变量 红色
nil
从上面的执行结果可以看出:在方法内访问类范围的实例变量时结果为nil,这表明方法内无法访问类范围的实例变量。
如果我们希望暴露类范围的实例变量,可以通过定义一个类方法来暴露它。看如下的代码:
- class Test
- # 定义一个类范围的实例变量@name,该实例变量属于Class类的实例(该实例名为Test)
- @name = "孙悟空";
- def name=(value)
- # 定义属于Test实例的实例变量
- @name = value
- end
- # 定义一个类方法
- def Test.name
- # 类方法的调用者是Test类本身,也就是Class类的实例
- # 因此,此处访问的是类范围的实例变量
- @name
- end
- end
- # 调用Test类的name方法
- puts Test.name