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中的保留字

LINE ENCODING FILE BEGIN
END alias and begin
break case def defined?
do else elsif end
ensure false for while
if in module next
nil not or redo
rescue retry return self
super then true undef
unless until when while
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等类,都是它的实例,因此类本质上也是对象,因此在调用方法时,只是为了区分接收者的对象类型,才有类方法和实例方法之区别。

发布了9 篇原创文章 · 获赞 4 · 访问量 178

猜你喜欢

转载自blog.csdn.net/tonyyong90/article/details/105607801
今日推荐