1. 类
1.1 创建类
在Ruby中创建一个类的例子如下:
class Test
def initialize(name)
@name = name
end
end
initialize是一个特殊的方法。当调用Test.new时,Ruby首先分配一些内存来保存未初始化的变量,然后调用对象的initialize方法,并把调用new时所使用的参数传入该方法。
实例变量是一个由"@"字符开头的名字。对象内的所有方法都可以访问实例变量,每个对象都有实例变量的一份拷贝。
Ruby有一个标准消息to_s,它可以发送给任何一个想要输出字符串表示的对象,例如:
test = Test.new('John')
test.to_s
在Ruby中,类永远都不是封闭的,总是可以向一个已有的类中添加方法,这同样适用于标准的内建类,例如:
class Test
def to_s
"Name: #@name"
end
end
1.2 继承和消息
继承允许创建一个类,作为另一个类的精炼和特化,例如:
class UITest < Test
def initialize(name, size)
super(name)
@size = size
end
end
类定义行中的"<"说明UITest是Test的子类,当Ruby遇到方法调用时,它会查看方法所属的类,如果该类实现了和消息名称相同的方法,就运行这个方法,否则,就会查看其父类中的方法,以此追溯整个祖先链。如果最终没有找到合适的方法,Ruby通常会引发一个错误。
关键字super要求向当前对象的父类发送一个消息,要求它调用子类中的同名方法。Ruby默认以Object类作为其父类,即所有类的始祖都是Object,to_s即是Object类的实例方法之一。
1.3 对象和属性
对象的外部可见部分被称为其属性,例如:
class Test
def name
@name
end
end
puts Test.name
Ruby提供了一种方便的快捷方式来创建对实例变量的访问方法,例如:
class Test
attr_reader :name
end
puts Test.name
有些时候,需要能够在对象外部设置它的属性,可以创建一个名字以等号结尾的方法来达成目标,例如:
class Test
def name=(new_name)
@name = new_name
end
end
Test.name = 'Marry'
puts Test.name
Ruby同样又提供了一种快捷方式来创建这类简单的属性设置方法,例如:
class Test
attr_writer :name
end
Test.name = 'Marry'
puts Test.name
在设计一个类的时候,可以决定其具有什么样的内部状态以及这些内部状态对外界的表现形式。内部状态保存在实例变量中,通过方法暴露出来的外部状态,称之为属性,而类可以执行的其他动作,则是一般方法。
1.4 类变量和类方法
类变量被类的所有对象所共享,对一个给定的类来说,类变量只存在一份拷贝。类变量由两个"@"符开头,类变量在使用之前必须被初始化。通常,初始化就是在类定义中的简单赋值,例如:
class Test
@@name = 'John'
end
类变量对类及其实例都是私有的,如果要在外部被访问,则需要编写方法,这个方法要么是一个实例方法,要么是类方法。
类方法和实例方法是通过它们的定义区别开来的,通过在方法名之前放置类名以及一个句点,来定义类方法,例如:
class Test
def Test.class_method
end
end
如果希望所有对象都共享同一个操作对象,可以使用单例模式,例如:
class Test
private_class_method :new
@@tester = nil
def Test.create
@@tester = new unless @@tester
@@tester
end
end
tester1 = Test.create
tester2 = Test.create
1.5 访问控制
Ruby提供了三种级别的保护:
1) Public: 方法可以被任何人调用,没有限制访问控制。方法默认是public的,除了initialize,它是private的。
2) Protected: 方法只能被定义了该方法的类或其子类的对象所调用。
3) Private: 方法不能被明确的接收者调用,其接收者只能是self。这意味着私有方法只能在当前对象的上下文中被调用。
可以使用public、protected和private三个函数为类或模块定义内的方法指定访问级别。如果使用时没有参数,这三个函数设置后续定义方法的默认访问控制,例如:
class Test
protected
def method1
end
private
def method2
end
public
def method3
end
end
另外,可以通过将方法名作为参数列表传入访问控制函数来设置它们的访问级别,例如:
class Test
def method1
end
def method2
end
def method3
end
def method4
end
public :method1 :method4
protected :method2
private :method3
end
1.6 变量
在Ruby中,变量是对象的一个引用,例如:
person1 = 'Tim'
person2 = person1
person1[0] = 'J'
puts person2 #Jim
在上例中,改变了person1的值,但是person1和person2都从"Tim"改为了"Jim"。这归结于变量保存的是对象引用而非对象本身。 可以通过使用String的dup方法来避免创建别名,例如:
person1 = 'Tim'
person2 = person1.dup
person1[0] = 'J'
puts person2 #Tim
可以通过冻结一个对象来阻止其他人对其进行改动,试图更改一个被冻结的对象,Ruby将引发一个TypeError异常,例如:
person1 = 'Tim'
person2 = person1
person1.freeze
person2[0] = 'J' #error
2. 容器
2.1 数组
数组类含有一组对象引用,每个对象引用占据数组的一个位置。可以通过使用字面量,或显式创建Array对象,来创建数组,例如:
myarray1 = [1, 'one']
myarray2 = Array.new
myarray2[0] = 1
myarray2[1] = 'one'
数组由"[]"操作符来进行索引,下标从0开始。使用非负整数访问数组,将会返回处于该整数位置上的对象,如果此位置上没有对象,则返回nil。如果使用负整数访问数组,则从数组末端开始计数。
也可以使用一对数字"[start, count]"来访问数组,这将返回一个包含从start开始的count个对象引用的新数组,例如:
a = [1, 2, 3, 4, 5]
newarray = a[1, 3]
最后,还可以使用range来对数组进行索引,其开始和结束位置被两个或3个点分割开,两个点的形式包含结束位置,而3个点的形式不包含,例如:
a = [1, 2, 3, 4, 5]
newarray1 = a[1..3]
newarray2 = a[1...3]
"[]"操作符有一个相应的"[]="操作符,它可以设置数组中的元素,替换造成的任何间隙将由nil来填充。如果"[]="的下标是两个数字或者是range,那么原数组中的那些元素将被赋值语句右边的东西所替换。如果长度是0,那么赋值语句右边的东西将被插入到数组的起点位置之前。如果右边本身是一个数组,那么其元素将替换掉原数组对应位置上的东西。如果索引下标选择的元素个数和赋值语句右边的元素个数不一致,那么数组会自动调整其大小。
2.2 散列表
散列表和数组的相似处在它们都是被索引的对象引用的集俣,不过数组只能用整数来索引,而散列表可以用任何类型的对象来进行索引,随后可以通过键去索引散列表以获得其对应的值,例如:
myhash = {'one' => 1, 'two' => 2}
和数组相比,散列表有一个突出的优点:可以用任何对象做索引,然后它也有一个突出的缺点:它的元素是无序的,因此很难用hash来实现栈和队列。
2.3 迭代器
Ruby的迭代器是可以调用block的方法,Ruby的代码区块和C、Java等不同,它不是传统意义上的、将语句组织在一起的一种方式。首先,block在代码中只和方法调用一起出现,其次,在遇到block的时候并不立刻执行其中的代码。Ruby会记住block出现时的上下文然后执行方法调用。
在方法内部,block可以像方法一样被yield语句调用,每执行一次yield,就会调用block中的代码,例如:
def test
yield
yield
yield
end
test {puts 'Hello World!'}
yield语句可以带有参数,参数值将传送给相关联的block,在block定义中,参数列表位于两个竖线之间,block可以有任意数量的参数。如果传递给block的参数是已存在的局部变蛳,那它们的值可能会因block的执行而改变。block也可以返回值给方法。
一些迭代器是Ruby的许多收集类型所共有的,比如find方法,例如:
myarray.find {|item| item == 1}
each可能是最简单的迭代器,它所做的就是连续访问收集的所有元素,例如:
myarray.each {|item| puts item}
collect迭代器从收集中获得各个元素并传递给block,block返回的结果被用来生成一个新的数组,例如:
[1, 2, 3].collect {|item| item+1}
inject迭代器可以遍历收集的所有成员以累计出一个值,例如:
[1, 3, 5].inject(0) {|sum, element| sum+element}