Ruby 面向对象
Ruby 是真正的,纯,面向对象的语言,Ruby 中的一切,都是以对象的形式出现。
Ruby 中的每个值,都是一个对象,即使是最原始的东西:字符串、数字,甚至连 true 和 false 都是对象。
类本身也是一个对象,是 Class 类的一个实例。
本章将向您讲解所有与 Ruby 面向对象相关的主要功能。
类用于指定对象的形式,它结合了数据表示法和方法,把数据整理成一个整齐的包。
类中的数据和方法被称为类的成员。
Ruby 类定义
当您定义一个类时,您实际是定义了一个数据类型的蓝图。
这实际上并没有定义任何的数据,而是定义了类的名称意味着什么,
也就是说,定义了类的对象将由什么组成,以及在该对象上能执行什么操作。
类定义以关键字 class 开始,后跟类名称,最后以一个 end 进行分隔表示终止该类定义。
例如,我们使用关键字 class 来定义 Box 类,如下所示:
class Box
code
end
按照惯例,名称必须以大写字母开头,如果包含多个单词,每个单词首字母大写,但此间没有分隔符(例如:驼峰命名法CamelCase)。
定义 Ruby 对象
类提供了对象的蓝图,所以基本上,对象是根据类进行创建的。
我们使用 new 关键字声明 类的对象。
下面的语句声明了类 Box 的两个对象:
box1 = Box.new
box2 = Box.new
initialize 方法
initialize 方法是一个标准的 Ruby 类方法,与其他面向对象编程语言中的 构造方法constructor 工作原理类似。
当您想要在创建对象的同时初始化一些类变量,initialize 方法就派上用场了。
该方法可以带有一系列参数,与其他 Ruby 方法一样,使用该方法时,必须在前面放置 def 关键字,
如下所示:
class Box
def initialize(width,height)
@_width, @_height = width, height
end
end
实例变量
实例变量是类属性,它们在使用类创建对象时 就变成了对象的属性。
每个对象的属性是单独赋值的,和其他对象之间不共享值。
在类的内部,是使用 @ 运算符访问这些属性,
在类的外部,则是使用称为访问器方法的公共方法进行访问。
下面我们以上面定义的类 Box 为实例,把 @_width 和 @_height 作为类 Box 的实例变量。
class Box
def initialize(width,height)
# 给实例变量赋值
@_width, @_height = width, height
end
end
访问器getter & 设置器setter 方法
为了在类的外部使用变量,我们必须在访问器方法内部定义这些变量,这些访问器方法也被称为获取器方法(getter)。
下面的实例演示了访问器方法的用法:
当上面的代码执行时,它会产生以下结果:
与用于访问变量值的访问器方法类似,Ruby 提供了一种在类的外部设置变量值的方式,也就是所谓的设置器方法setter,
定义如下:
当下面的代码执行时,它会产生以下结果:
#!/usr/bin/ruby -w
# -*- coding: UTF-8 -*-
#coding=utf-8
class Girl
#构造方法
def initialize(name,age)
#连续赋值
@_name,@_age = name,age
end
#getter方法
def getName
@_name
end
#getter方法
def getAge
@_age
end
#setter方法
def setAge=(age)
@_age = age;
end
end
#主函数
menma = Girl.new("面码",6)
#使用访问器方法getter
age = menma.getAge
#十年之后,setter方法设置新的年龄(注意写法!!!)
menma.setAge = age + 10
print menma.getName,"的年龄是",menma.getAge,"\n"
运行结果:
实例方法
实例方法的定义与其他方法的定义一样,都是使用 def 关键字,
但它们只能通过类实例来使用,如下面实例所示。
它们的功能不限于访问实例变量,也能按照您的需求做更多其他的任务。
类方法 & 类变量
类变量 是在类的所有实例中共享的变量。
换句话说,类变量的实例可以被所有的对象实例访问。
类变量以两个 @ 字符(@@)作为前缀,类变量必须在类定义中被初始化,如下面实例所示。
类方法使用 def self.methodName() 定义,类方法以 end 分隔符结尾。
类方法可使用带有类名称的 ClassName.methodName 形式调用,如下面实例所示:
to_s 方法
您定义的任何类都有一个 to_s 实例方法来返回对象的字符串表示形式(类似于Java的toString)。
下面是一个简单的实例,根据 width 和 height 表示 Box 对象:
当上面的代码执行时,它会产生以下结果:
访问控制
Ruby 为您提供了三个级别的实例方法保护,分别是 public、private 或 protected。
Ruby 不在 实例和类变量 上应用任何访问控制。
- Public 方法: Public 方法可被任意对象调用。默认情况下,方法都是 public 的,除了 initialize 方法总是 private 的。
- Private 方法: Private 方法不能从类外部访问或查看。只有类方法可以访问私有成员。
- Protected 方法: Protected 方法只能被类及其子类的对象调用。访问也只能在类及其子类内部进行。
下面是一个简单的实例,演示了这三种修饰符的语法:
当下面的代码执行时,它会产生以下结果。在这里,第一种方法调用成功,但是第二方法会产生一个问题:找不到方法。
#!/usr/bin/ruby -w
# -*- coding: UTF-8 -*-
#coding=utf-8
class Girl
#构造方法
def initialize(name,age,cup)
#连续赋值
@_name,@_age,@_cup = name,age,cup
end
#定义私有方法演示
def getCup
@_cup
end
private :getCup
#也可以同时指定多个方法为私有方法或者protected方法,如下所示
#private :getCup, :getName, :getAge
#protected :getCup, :getName, :getAge
#默认的实例方法都是公有方法
def showGirlCup
#内部可以访问 private方法和protected方法
print @_name,"'s cup is ",getCup(),"\n"
end
end
#主函数
menma = Girl.new("面码",16,"AA")
#调用默认的public方法打印罩杯
menma.showGirlCup();
#尝试调用私有的方法打印罩杯
menma.getCup();
类的继承
继承,是面向对象编程中最重要的概念之一。
继承允许我们根据另一个基类定义一个子类,这样使得创建和维护应用程序变得更加容易。
继承有助于重用代码和快速执行,不幸的是,Ruby 不支持多继承,但是 Ruby 支持 mix-ins。
mix-in 就像是多继承的一个特定实现,在多继承中,只有接口部分是可继承的。
当创建类时,程序员可以直接指定新类继承自某个已有类,这样就不用从头编写新的数据成员和成员函数。
这个已有类被称为基类或父类,新类被称为派生类或子类。
Ruby 也提供了子类化的概念,子类化即继承,下面的实例解释了这个概念。
扩展一个类的语法非常简单。只要添加一个 < 字符和父类的名称到类语句中即可。
例如,下面定义了类 Girl 是 Person 的子类:
当上面的代码执行时,它会产生以下结果:
方法重载
虽然您可以在派生类中添加新的功能,但有时您可能想要改变已经在父类中定义的方法的行为。
这时您可以保持方法名称不变,重载方法的功能即可,如下面实例所示:

如果我们希望使用 + 运算符执行两个 Box 对象的向量加法,
如果我们希望使用 * 运算符来把 Box 的 width 和 height 相乘,
如果我们希望使用一元运算符 - 对 Box 的 width 和 height 求反。
那么我们就需要对运算符进行重载
下面是一个带有数学运算重载符定义的 Box 类版本:
#!/usr/bin/ruby -w
# -*- coding: UTF-8 -*-
#coding=utf-8
class Rectangle
def initialize(width,length)
@_width,@_length = width,length
end
#getter方法
def getWidth
@_width
end
def getLength
@_length
end
#重载+号
def +(otherOne)
Rectangle.new(@_width + otherOne.getWidth(),@_length + otherOne.getLength())
end
#打印
def printArea
area = @_width * @_length
puts "#{@_width} * #{@_length} = #{area}"
end
end
#主函数
rect_1 = Rectangle.new(6,7)
rect_1.printArea();
rect_2 = Rectangle.new(5,20)
rect_2.printArea();
rect_3 = rect_1 + rect_2
rect_3.printArea();
冻结对象
有时候,我们想要防止对象被改变。
在 Object 中,freeze 方法可实现这点,它能有效地把一个对象变成一个常量。
任何对象都可以通过调用 objectInstance.freeze 进行冻结。
冻结对象不能被修改,也就是说,您不能改变它的实例变量。
您可以使用 objectInstance.frozen? 方法检查一个给定的对象是否已经被冻结。
如果对象已被冻结,该方法将返回 true,否则返回一个 false 值。
下面的实例解释了这个概念:
#!/usr/bin/ruby -w
# -*- coding: UTF-8 -*-
#coding=utf-8
class Girl
def initialize(name,age)
@_name,@_age = name,age
end
#getter方法
def getName
@_name
end
def getAge
@_age
end
#setter方法
def setAge=(newAge)
@_age = newAge
end
end
#主函数
menma = Girl.new("面码",6)
#面码6岁那年去世
menma.freeze
if (menma.frozen?)
puts "面码's time is frozen at #{menma.getAge()} 岁"
else
puts "面码 is still alive"
end
#尝试对已经frozen的面码进行setAge (将会失败)
menma.setAge = menma.getAge() + 10
#尝试重新获取一下age
newAge = menma.getAge()
#打印一下重新获取的age
puts "面码's age is #{newAge}"
当上面的代码执行时,它会产生以下结果:
类常量
您可以在类的内部定义一个常量,通过把一个直接的数值或字符串值赋给一个变量来定义的,
常量的定义不需要使用 @ 或 @@
按照国际惯例,常量的名称全部使用大写
一旦常量被定义,您就不能改变它的值,您可以在类的内部直接访问常量,就像是访问变量一样,
但是如果您想要在类的外部访问常量,那么您必须使用 ClassName::ConstantName,如下面实例所示。
当下面的代码执行时,它会产生以下结果:
类常量可被继承,也可像实例方法一样被重载。
使用 allocate 创建对象
可能有一种情况,您想要在 不调用对象构造器 initialize (即不使用 new 方法)的情况下创建对象,
在这种情况下,您可以调用 allocate 来创建一个未初始化的对象,
如下面实例所示:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
class Girl
#attr_accessor相当于attr_reader和attr_writer的合集
#实际上是在定义类成员变量的时候就给他定义了一个get和set方法
#在ruby中,类成员变量默认都是私有的
#不能直接通过(类名.成员变量名)这样来对成员变量值进行操作
#但是,使用attr_accessor定义的成员可以直接对其进行set和get的操作
#如果没有attr_accessor,则不行。
attr_accessor :_name, :_age
#构造方法
def initialize(name,age)
@_name,@_age = name,age
end
#定义实例方法
def showActressForAnime(anime)
puts "「#{@_name}」is『#{anime}』女主,#{@_age}岁"
end
#十年后的年龄
def ageAfterTenYears
puts "「#{@_name}」十年后的年龄:#{@_age + 10}岁"
end
end
#主函数
menma = Girl.new("面码",6)
#打印动漫<未闻花名>里的女主角:面码
menma.showActressForAnime("未闻花名")
menma.ageAfterTenYears();
#如果使用allocate方法创建对象,则不会自动执行initialize方法对成员变量初始化
tiger = Girl.allocate
#打印动漫<龙与虎>里的女主角:逢坂大河
tiger.showActressForAnime("龙与虎")
#这儿会报错: +号方法不存在
tiger.ageAfterTenYears()
当上面的代码执行时,它会产生以下结果:
类信息
如果类定义是可执行代码,这意味着,它们可在某个对象的上下文中执行,
self后面必须引用一些属性名称,如self.name,或者self.class等。
让我们来看看下面的实例:
当下面的代码执行时,它会产生以下结果:
这意味着 类定义 可通过 把该类作为当前对象来执行,
同时也意味着 元类和父类中的方法 在方法定义执行期间是可用的。???Excuse Me???
Ruby 正则表达式
正则表达式是一种特殊序列的字符,它通过使用有专门语法的模式来匹配或查找其他字符串或字符串集合。
语法
正则表达式从字面上看是一种介于斜杠之间/***/
或者是 介于 跟在 %r 后的任意分隔符之间的模式,
如下所示:
/pattern/
/pattern/im # 可以指定匹配选项
%r!/usr/local! # 一般的分隔的正则表达式
实例ruby_119.rb代码如下,将产生以下结果:
正则表达式修饰符
正则表达式从字面上看可能包含一个可选的修饰符,用于控制各方面的匹配。
修饰符在第二个斜杠字符后面,如上面实例所示。
下标列出了 可能的修饰符:
修饰符 | 描述 |
---|---|
i | 当匹配文本时忽略大小写。ignore |
o | 只执行一次 #{} 插值,正则表达式在第一次时就进行判断???Excuse Me??? |
x | 忽略空格,允许在正则表达式中进行注释。 |
m | 匹配多行,把换行字符识别为正常字符。multiple line |
u,e,s,n | 把正则表达式解释为 Unicode(UTF-8)、EUC、SJIS 或 ASCII。 如果没有指定修饰符,则认为正则表达式使用的是源编码。 |
就像字符串通过 %Q 进行分隔一样,Ruby 允许您以 %r 作为正则表达式的开头,后面跟着任意分隔符。
这在描述包含大量您不想转义的斜杠字符时非常有用。
# 下面匹配单个斜杠字符,不转义
%r|/|
# Flag 字符可通过下面的语法进行匹配,ignore 忽略大小写
%r[</(.*)>]i #匹配</a>等结束标签
正则表达式模式
除了控制字符 (+ ? . * ^ $ ( ) [ ] { } | \)外, 其他所有字符都匹配本身。
您可以通过在控制字符前放置一个反斜杠来对控制字符进行转义。
下表列出了 Ruby 中可用的正则表达式语法。
模式 | 描述 |
---|---|
^ | 匹配行的开头。 |
$ | 匹配行的结尾。 |
. | 匹配除了换行符以外的任意单字符。 使用 m 选项时,它也可以匹配换行符。multiple line |
[...] | 匹配在方括号中的任意单字符。 |
[^...] | 匹配不在方括号中的任意单字符。 |
re* | 匹配前面的子表达式零次或多次。 |
re+ | 匹配前面的子表达式一次或多次。 |
re? | 匹配前面的子表达式零次或一次。有或者没有 |
re{ n} | 匹配前面的子表达式 n 次。 |
re{ n,} | 匹配前面的子表达式 n 次或 n 次以上。 |
re{ n, m} | 匹配前面的子表达式至少 n 次至多 m 次。 |
a| b | 匹配 a 或 b。 |
(re) | 对正则表达式进行分组,并记住匹配文本。 |
(?imx) | 暂时打开正则表达式内的 i、 m 或 x 选项。 如果在圆括号中,则只影响圆括号内的部分。 |
(?-imx) | 暂时关闭正则表达式内的 i、 m 或 x 选项。 如果在圆括号中,则只影响圆括号内的部分。 |
(?: re) | 只对正则表达式进行分组,但不记住匹配文本。???Excuse Me??? |
(?imx: re) | 暂时打开圆括号内的 i、 m 或 x 选项。 ???Excuse Me??? |
(?-imx: re) | 暂时关闭圆括号内的 i、 m 或 x 选项。 ???Excuse Me??? |
(?#...) | 注释 |
(?= re) | 使用模式指定位置。没有范围。 文本的后面必须跟着re |
(?! re) | 使用模式的否定指定位置。没有范围。 文本的后面必须不是re |
(?> re) | 匹配无回溯的独立模式???Excuse Me??? |
\w | 匹配单词字符。 |
\W | 匹配非单词字符。 |
\s | 匹配空白字符。等价于 [\t\n\r\f]。 |
\S | 匹配非空白字符。 |
\d | 匹配数字。等价于 [0-9]。 |
\D | 匹配非数字。 |
\A | 匹配字符串的开头。 |
\Z | 匹配字符串的结尾。如果存在换行符,则只匹配到换行符之前。 |
\z | 匹配字符串的结尾。 |
\G | 匹配最后一个匹配完成的点???Excuse Me??? |
\b | 当在括号外时匹配单词边界,当在括号内时匹配退格键(0x08)。 |
\B | 匹配非单词边界。 |
\n, \t, etc. | 匹配换行符、回车符、制表符,等等。 |
\1...\9 | 匹配第 n 个分组子表达式。 |
\10 | 如果已匹配过,则匹配第 n 个分组子表达式。否则指向字符编码的八进制表示。??? |
补充一点:\w代表的是数字或字母或下划线,但在Ruby中不包括汉字
下面的正则匹配的是有N个 或者 没有 \w的情况,
其中\w代表的是数字或字母或下划线,但在Ruby中不包括汉字,
正则匹配成功的话,b = 0, 所以if b是成立的,因为在ruby中,0也是真,只有false和nil是假
示例代码如下图所示:
正则表达式实例
字符
实例 | 描述 |
---|---|
/ruby/ | 匹配 "ruby" |
¥ | 匹配 Yen 符号。Ruby 1.9 和 Ruby 1.8 支持多个字符。 |
字符类
实例 | 描述 |
---|---|
/[Rr]uby/ | 匹配 "Ruby" 或 "ruby" |
/rub[ye]/ | 匹配 "ruby" 或 "rube" |
/[aeiou]/ | 匹配任何一个小写元音字母 |
/[0-9]/ | 匹配任何一个数字,与 /[0123456789]/ 相同 |
/[a-z]/ | 匹配任何一个小写 ASCII 字母 |
/[A-Z]/ | 匹配任何一个大写 ASCII 字母 |
/[a-zA-Z0-9]/ | 匹配任何一个括号内的字符 |
/[^aeiou]/ | 匹配任何一个非小写元音字母的字符 |
/[^0-9]/ | 匹配任何一个非数字字符 |
特殊字符类
实例 | 描述 |
---|---|
/./ | 匹配除了换行符以外的其他任意字符 |
/./m | 在多行模式下,也能匹配换行符 |
/\d/ | 匹配一个数字,等同于 /[0-9]/ |
/\D/ | 匹配一个非数字,等同于 /[^0-9]/ |
/\s/ | 匹配一个空白字符,等同于 /[ \t\r\n\f]/ |
/\S/ | 匹配一个非空白字符,等同于 /[^ \t\r\n\f]/ |
/\w/ | 匹配一个单词字符,等同于 /[A-Za-z0-9_]/ |
/\W/ | 匹配一个非单词字符,等同于 /[^A-Za-z0-9_]/ |
重复
实例 | 描述 |
---|---|
/ruby?/ | 匹配 "rub" 或 "ruby"。其中,y 是可有可无的。有或者没有 |
/ruby*/ | 匹配 "rub" 加上 0 个或多个的 y。 |
/ruby+/ | 匹配 "rub" 加上 1 个或多个的 y。 |
/\d{3}/ | 刚好匹配 3 个数字。 |
/\d{3,}/ | 匹配 3 个或多个数字。 |
/\d{3,5}/ | 匹配 3 个、4 个或 5 个数字。 |
非贪婪重复
这会匹配最小次数的重复。
实例 | 描述 |
---|---|
/<.*>/ | 贪婪重复:匹配 "<ruby>perl>" |
/<.*?>/ | 非贪婪重复:匹配 "<ruby>perl>" 中的 "<ruby>" |
通过圆括号进行分组
实例 | 描述 |
---|---|
/\D\d+/ | 无分组: + 重复 \d |
/(\D\d)+/ | 分组: + 重复 \D\d 组合 |
/([Rr]uby(, )?)+/ | 匹配 "Ruby"、"Ruby, ruby, ruby",等等 ( [Rr]uby(, )? )+ |
反向引用
这会再次匹配之前匹配过的分组。
实例 | 描述 |
---|---|
/([Rr])uby&\1ails/ | 匹配 ruby&rails 或 Ruby&Rails ([Rr]) uby & \1 ails |
/(['"])(?:(?!\1).)*\1/ | 单引号或双引号字符串。\1 匹配第一个分组所匹配的字符,\2 匹配第二个分组所匹配的字符,依此类推。???Excuse Me??? |
替换
实例 | 描述 |
---|---|
/ruby|rube/ | 匹配 "ruby" 或 "rube" |
/rub(y|le))/ | 匹配 "ruby" 或 "ruble" |
/ruby(!+|\?)/ | "ruby" 后跟一个或多个 ! 或者跟一个 ? |
锚
这需要指定匹配位置。
实例 | 描述 |
---|---|
/^Ruby/ | 匹配以 "Ruby" 开头的字符串或行 |
/Ruby$/ | 匹配以 "Ruby" 结尾的字符串或行 |
/\ARuby/ | 匹配以 "Ruby" 开头的字符串 |
/Ruby\Z/ | 匹配以 "Ruby" 结尾的字符串 |
/Ruby/ | 匹配单词边界的 "Ruby" |
/rub\B/ | \B 是非单词边界:匹配 "rube" 和 "ruby" 中的 "rub",但不匹配单独的 "rub" |
/Ruby(?=!)/ | 如果 "Ruby" 后跟着一个感叹号,则匹配 "Ruby" |
/Ruby(?!!)/ | 如果 "Ruby" 后没有跟着一个感叹号,则匹配 "Ruby" |
圆括号的特殊语法
实例 | 描述 |
---|---|
/R(?#comment)/ | 匹配 "R"。所有剩余的字符都是注释。 |
/R(?i)uby/ | 当匹配 "uby" 时不区分大小写???Excuse Me???暂时打开i选项 |
/R(?i:uby)/ | 与上面相同。???Excuse Me??? |
/rub(?:y|le))/ | 只分组,不进行 \1 反向引用 ???Excuse Me??? |
搜索和替换
sub 和 gsub 及它们的替代变量 sub! 和 gsub! 是使用正则表达式时,重要的字符串方法。
所有这些方法都是使用正则表达式模式执行搜索与替换操作。
sub 和 sub! 替换 模式的第一次出现,
gsub 和 gsub! 替换 模式的所有出现(g就是global)
sub 和 gsub 返回一个新的字符串,保持原始的字符串不被修改,
而当有感叹号!结尾时 ,表示会同时修改原来的字符串
sub! 和 gsub! 则会修改它们调用的原有的字符串。
下面是一个实例:
这将产生以下结果:
下面是另一个实例:
未完待续,下一章节,つづく