Ruby中的类和模块和变量使用说明

对象, 变量, 常量和类

在ruby中表现数据的基本单位称为对象。

常见对象与其所属的类

对象
数值Numeric
字符串String
散列Hash
正则表达式Regex
文件File
符号Symbol
数组Array

此外还有range和execption对象等

变量分为以下几种

  • 全局变量: 以$来表示
  • 实例变量: 以@开头来表示
  • 类变量: 以@@开头来表示
  • 局部变量: 以英文字母或者_来表示

除了以上几种变量类型 还有伪变量类型(一种在ruby里面预先定义的变量类型)

Ruby中,nil、true、false、self等都是伪变量

全局变量与局部变量示例
新建两个文件,一个为scopetest.rb, 一个为variable.rb
variable.rb内容
$x = 1
x = 1

scopetest.rb内容
$x = 200
require_relative 'sub'

x =  3
p $x
p x
结果为:1  3
常量以大写字母命名
且一旦赋值以后就不在可以被更改,如果赋值会产生异常。

ruby中的保留字

LINEENCODINGFILEBEGIN
ENDaliasandbegin
breakcasedefdefined?
doelseelsifend
ensurefalseforwhile
ifinmodulenext
nilnotorredo
rescueretryreturnself
superthentrueundef
unlessuntilwhenwhile
yield

类和模块

当想知道某个对象属于哪个类时,我们可以使用class方法

ary = []
str = "hello world"
p ary.class #=> Array
p str.class #=> String

当想知道某个对象是否属于某个类时,我们可以使用instance_of?方法
ary=[]
str="Helloworld."
p ary.instance_of?(Array)  #=>true
p str.instance_of?(String) #=>true
p ary.instance_of?(String) #=>false
p str.instance_of?(Array)  #=>false

继承

通过扩展已定义的类来创建新类称为继承。
继承后创建的新类称为子类(subclass),被继承的类称为父类2(superclass)。通过继承可以实现以下操作。

  • 在不影响原有功能的前提下追加新功能
  • 重新定义原有功能, 使名称相同的方法产生不同的效果
  • 在已有功能的基础上追加处理,扩展已有功能
BasicObject类是Ruby中所有类的父类,它定义了作为Ruby对象的最基本功能。备注BasicObject类是最最基础的类,甚至连一般对象需要的功能都没有定义。因此普通对象所需要的类一般都被定义为Object类。字符串、数组等都是Object类的子类。

子类与父类的关系称为“isa关系”3。例如,String类与它的父类Object就是isa关系。
str = "hello" ;
p str.is_a?(String) #=> true
p str.is_a?(Object) #=> true
p str.is_a?(BasicObject) #=> true
由于instance_of?方法与is_a?方法都已经在Object类中定义过了,因此普通的对象都可以使用这两个方法。

示例:创建一个类
class HelloWorld                   # class 语句

   def initialize(myname = "Ruby") # 类方法
      @name = myname               # 初始化实例变量
   end

   def hello                       # 实例方法
      puts "hello, world. I am #{@name}"
   end
end

bob = HelloWorld.new("Bob")
alice = HelloWorld.new("Alice")
ruby = HelloWorld.new("Ruby")
bob.hello
alice.hello
ruby.hello

class 类名  
类的定义
end

类名的首字母必须大写。

名为initialize的方法比较特别。使用new方法生成新的对象时,initialize方法会被调用,同时new方法的参数也会被原封不动地传给initialize方法。因此初始化对象时需要的处理一般都写在这个方法中。

而只要在同一个实例中,程序就可以超越方法定义,任意引用、修改实例变量的值。另外,引用未初始化的实例变量时的返回值为nil
存取器
Ruby中,从对象外部不能直接访问实例变量或对实例变量赋值,需要通过方法来访问对象的内部。
对类进行扩充,增加存取方法:

class HelloWorld                   # class 语句

   def initialize(myname = "Ruby") # 类方法
      @name = myname               # 初始化实例变量
   end

   def hello                       # 实例方法
      puts "hello, world. I am #{@name}"
   end

   def name=(name) # 存
      @name = name
   end
 
   def name       # 取
      return @name
   end
end

bob = HelloWorld.new("Bob")
bob.hello

bob.name =("kobe")
p bob.name
bob.name =("kobe") #=> 实例变量@name的值此时为kobe
p bob.name
当对象越来越多时,需要定义的存取器越来越多,导致代码越来越复杂。因此ruby提供了存取器的快捷方式
定义意义
attr_reader :name只读(定义name方法)
attr_writer :name只写(定义 name=方法)
attr_accessor :name读写(定义以上两个方法)
在实例方法中,可以用self这个特殊的变量来引用方法的接收者

class HelloWorld                   # class 语句

   attr_accessor :name
   def initialize(myname = "Ruby") # 类方法
      @name = myname               # 初始化实例变量
   end

   def greet
      puts "Hi, I am #{self.name}"
   end

   def greet_again
      puts "Hi, I am #{name}"
   end

bob = HelloWorld.new("Bob")
bob.greet #=> Hi, I am Bob
bob.greet_again #=> Hi, I am Bob
调用方法时,如果省略了接收者,Ruby就会默认把self作为该方法的接收者。因此,即使省略了self,也还是可以调用name方法

但是有一点需要注意:
在调用像name=方法这样的以=结束的方法时,即使实例方法中已经有了name="Ruby"这样的定义,但如果仅在方法内部定义名为name的局部变量,也不能以缺省接收者的方式调用name=方法。这种情况下,我们需要用self.name="Ruby"的形式来显式调用name方法。

备注 虽然self本身与局部变量形式相同,但由于它是引用对象本身时的保留字,因此即使对它进行赋值,也不会对其本身的值有任何影响。像这样,已经被系统使用且不能被我们自定义的变量名还有niltruefalse、__FILE__、__LINE__、__ENCODING__等。

如何在已经定义的类中追加新的类方法?
方法的接收者就是类本身(类对象)的方法称为类方法。

第一种:
class << 类名 ~ end 这个特殊的类定义中,以定义实例方法的形式来定义类方法。

class << HelloWorld
   def hello(name)
      puts "#{name} said hello"
   end
end

HelloWorld.hello("Ruby")

第二种:
在class语句中使用self时,引用的对象是该类本身,因此,我们可以使用class << self ~ end 这样的形式,在class语句中定义类方法。这种方式很常见

class HelloWorld
   class << self
      def greet(name)
         puts "#{name} said Hello"
      end
   end

end

HelloWorld.greet("Perl")

第三种:
我们还可以使用 def 类名.方法名 ~ end 这样的形式来定义类方法。
class HelloWorld
   def self.speak(name)
      puts "#{name} said hello"
   end
end

HelloWorld.speak("Matz")

备注 class << 类名 ~ end 这种写法的类定义称为单例类定义,单例类定义中定义的方法称为单例方法。

常量
常量就是指拥有固定值,且值一旦被定义就无法更改的变量,通常用大写字母来命名。
对于在类中定义的常量,我们可以像下面这样使用::通过类名来实现外部访问。

类变量
以@@开头的变量称为类变量。类变量是该类所有实例的共享变量,这一点与常量类似,不同的是我们可以多次修改类变量的值。另外,与实例变量一样,从类的外部访问类变量时也需要存取器。不过,由于attr_accessor等存取器都不能使用,因此需要直接定义。

class HelloCount
   @@count = 0

   def self.hello
      @@count += 1
      puts "hello #{@@count}"
   end

   def self.counts
      @@count
   end
end

HelloCount.hello
HelloCount.hello
HelloCount.hello
p HelloCount.counts

限制方法的调用
到目前为止,我们定义的方法都能作为实例方法被任意调用,但是有时候我们可能并不希望这样。例如,只是为了汇总多个方法的共同处理而定义的方法,一般不会公开给外部使用。

Ruby提供了3种方法的访问级别,我们可以按照需要来灵活调整。

  • public…以实例方法的形式向外部公开该方法
  • private…在指定接收者的情况下不能调用该方法(只能使用默认接收者的形式调用)
  • protected…在同一个类中时可将该方法作为实例方法调用
publicprivate 的例子

class AccTest
   def pub
      puts "pub is a public meth!"
   end

   public :pub

   def priv
      puts "priv is a private meth"
   end

   private :priv

end

acc = AccTest.new
acc.pub #=> 正常调用
acc.priv #=> 报错

希望统一定义多个方法的访问级别时,可以使用下面的语法。
class AccTest
   public
   
   def pub
      puts "pub is a public meth!"
   end
   
   private
   def priv
      puts "priv is a private meth"
   end
end

没有指定访问级别的放马默认为public,但initialize方法是列外,它通常被定义为private。

定义为protected的方法,在同一个类(及其子类)中可作为实例方法使用,而在除此以外的地方则无法使用。
示例:
class Point
   attr_accessor :x, :y
   protected :x=, :y=

   def initialize(x = 0.0, y = 0.0)
      @x, @y = x, y
   end

   def swap(other)
      tmp_x, tmp_y = @x, @y
      self.x, self.y = other.x, other.y
      other.x, other.y = tmp_x, tmp_y

      return self
   end
end

obj = Point.new
obj2 = Point.new(30, 40)

obj.swap(obj2)
p [obj.x, obj.y]
p [obj2.x, obj2.y]
p obj2.x=20 #=> 错误❌,因为方法不是public方法,无法在类外部调用
给原有的类添加方法
Ruby允许我们在已经定义好的类中添加方法。
class String
   def count_str
      ary = self.split(/\s+/)
      return ary.size
   end
end

str = "Just another ruby newbie"
p str.count_str #=> 4
继承
class 类名 < 父类名
类定义
end

示例:重新定义[]运算符
其中super来调用父类中的[]方法
class RingArray < Array

   def [](i)
      idx = i % self.size
      super (idx)
   end
end

obj = RingArray[1, 2, 3, 4]
p obj[4]

定义类时没有指定父类的情况下,Ruby会默认该父类为Object类。
Object类提供了很多便于实际编程的方法,但在某些情况下希望使用更加轻量的类,
而这时就可以使用BasicObject类。
BasicObject 类只提供了作为Ruby对象的最低限度的方法。类对象调用 instance_methods 方法后,就会以符号的形式返回该类的实例方法列表。下面我们就用这个方法来对比一下Object类和BasicObject类的实例方法。
定义BasicObject的子类时,与Object类不同,需要明确指定BasicObject类为父类。

alias与undef
有时我们会希望给已经存在的方法设置别名,这种情况下就需要使用alias方法。alias方法的参数为方法名或者符号名。

alias 别名 原名   # 直接使用方法名
alias :别名 :原名  # 使用符号名

class C1

   def hello
      puts "hello"
   end
end

class C2 < C1
   alias old_hello hello
   def hello
      puts "hello again"
   end
end

class C2 < C1
   alias :old_hello :hello
   def hello
      puts "hello again"
   end
end

C2.new.hello
C2.new.old_hello

undef  方法名   # 直接使用方法名
undef  :方法名  # 使用符号名例如,在子类中希望删除父类定义的方法时,可以使用undef。

单例类
在下面的例子中,我们分别将"Ruby"赋值给str1对象和str2对象,然后只对str1对象添加hello方法。这样一来,两个对象分别调用hello方法时,str1对象可以正常调用,但str2对象调用时程序就会发生错误。

class << str
   def hello
      puts self.size
   end
end

str2 = ""
str.hello #=> 5
str2.hello #=>  No method error

到目前为止,我们已经多次只在特定的某个类中添加类方法。Ruby中所有的类都是Class类的对象,因此,Class类的实例方法以及类对象中所添加的单例方法都是类方法。

模块

模块是Ruby的特色功能之一。如果说类表现的是事物的实体(数据)及其行为(处理),那么模块表现的就只是事物的行为部分。模块与类有以下两点不同。

  • 模块不能拥有实例
  • 模块无法被继承、

Mixin就是将模块混合到类中。在定义类时使用include,模块中的方法、常量就都能被类使用。

  • 虽然两个类拥有相似的功能,但是不希望把它们作为相同的种类(Class)来考虑
  • Ruby不支持父类的多重继承,因此无法对已经继承的类添加共通的功能
module MyMoudle
   def hello
      puts "hello I am Moudle"
   end
end

class MyClass1
   include MyMoudle
end

class MyClass2
   include MyMoudle
end

MyClass1.new.hello
MyClass2.new.hello

模块可以提供独立的命名空间
所谓命名空间(namespace),就是对方法、常量、类等名称进行区分及管理的单位。由于模块提供各自独立的命名空间,因此A模块中的foo方法与B模块中的foo方法就会被程序认为是两个不同的方法。同样,A模块中的FOO常量与B模块的FOO常量也是两个不同的常量。
例如,在FileTest模块中存在与获取文件信息相关的方法。我们使用“模块名.方法名”的形式来调用在模块中定义的方法,这样的方法称为模块函数。

p FileTest.exist?('case.rb')
p FileTest.size("case.rb")
p Math::PI
p Math.sqrt(2)

如果没有定义与模块内的方法、常量等同名的名称,那么引用时就可以省略模块名。通过include可以把模块内的方法名、常量名合并到当前的命名空间。下面是刚才提到的Math模块的例子。

我们使用module语句来创建模块。语法与创建类时几乎相同,模块名的首字母必须大写。
module 模块名  
    模块定义
end

module HelloModule
   Version = "1.0"

   def hello(name)
      puts "Hello, #{name}."
   end

   module_function :hello
end

p HelloModule::Version
HelloModule.hello("Ruby")
HelloModule.__send__(:hello, "Perl")
备注:如果只是定义了方法,但是并没有通过 module_function symbol 方法将模块函数公开给外部使用的话,无法通过模块名.方法名的形式调用。 一旦通过module_function symbol 公开方法以后,就能以这种方法调用方法。

以“模块名.方法名”的形式调用时,如果在方法中调用self(接收者),就会获得该模块的对象。
def myself
      self
   end
p HelloModule.myself #=> HelloModule 模块名

Mix-in
Note: 关于Mix-in
当我们想要知道一个类的继承关系时可以使用xxx.ancestors 来获取
p Regexp.ancestors #=> [Regexp, Object, Kernel, BasicObject]

当我们想要知道一个类的父类可以用 Regexp.superclass方法来获取


假设有个类C,C的实例在调用方法时,Ruby会按类C、模块M、类C的父类Object这个顺序查找该方法,并执行第一个找到的方法。被包含的模块的作用就类似于虚拟的父类。
虽然Ruby采用的是不允许具有多个父类的单一继承模型,但是通过利用Mixin,就既可以保持单一继承的关系,又可以同时让多个类共享功能。
单一继承的优点就是简单,不会因为过多的继承而导致类之间的关系变得复杂。但是另一方面,有时我们又会希望更加积极地重用已有的类,或者把多个类的特性合并为更高级的类,在那样的情况下,灵活使用单一继承和Mixin,既能使类结构简单易懂,又能灵活地应对各种需求。

查找方法的规则
1.同继承关系一样,原类中已经定义了同名的方法时,优先使用该方法。
2.在同一个类中包含多个模块时,优先使用最后一个包含的模块。
3.嵌套include时,查找顺序也是线性的.
4.相同的模块被包含两次以上时,第二次以后的会被省略。


extend方法
extend方法可以使单例类包含模块,并把模块的功能扩展到对象中。
module A
   def name
      puts "I am A"
   end
end

str = "Hello"
str.extend(A)
str.name

class V

end

V.extend(A)
V.name
extend既可以用来扩展实例方法,也可以扩展类方法。见上面的示例。



module A
   def name
      puts "I am A"
   end
end

module B
   def name
      puts "I am B"
   end
end

module C
   include B
   def name
      puts "I am C"
   end
end

class TestModule
   include C
   include A
end

p TestModule.ancestors #=> [TestModule, A, C, B, Object, Kernel, BasicObject]

class TestModule
   include C, A
end

p TestModule.ancestors #=> [TestModule, C, B, A, Object, Kernel, BasicObject]
注意这里导入模块时,第一种方式是分行,第二种是用逗号隔开,先后顺序有区别,参考结果。
逗号之前的模块会被优先查找,而分行形式,最后倒入的模块会被优先查找。



如果类 Mix-in 了模块,就相当于为该类添加了实例方法,这种情况下self代表的就是被mix-in的类的对象。

即使是相同的方法,在不同的上下文调用时,其含义也会不一样,因此对于Mixin的模块,我们要注意根据实际情况判断是否使用模块函数功能。一般不建议在定义为模块函数的方法中使用self。

除了之前介绍的定义类方法的语法外,使用 extend 方法也同样能为类对象追加类方法。下面是使用 extend 方法追加类方法,并使用 include 方法追加实例方法的一个例子。

module ClassMethod
   def cmethod
      "class method"
   end
end

module InstanceMethods
   def imethod
      "instance method"
   end
end

class MyClass
   extend ClassMethod
   include InstanceMethods
end


p MyClass.cmethod
p MyClass.new.imethod

在这里,extend 与 include的区别在于 extend 用于扩展类方法,而include是扩展实例方法,这个不能搞错。
其实不管是类也好,实例也好,本质上都是对象,因为对于Class类来说,String等类,都是它的实例,因此类本质上也是对象,因此在调用方法时,只是为了区分接收者的对象类型,才有类方法和实例方法之区别。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值