Ruby Study 9 : Passing Arguments and Returning Values

简介:
1. Summarizing Instance, Class and Singleton methods
Instance methods指的是可以被它所在的类的实例调用的方法.
Class 方法和Singleton 方法指属于某个单独的对象的方法,不能被其他对象调用.(注意我把类方法也这么解释, 是因为类(包括BasicObject,Object,Module,Class以及自定义的类等)其实都是Class的对象.)

2. Returning Values
在很多编程语言中一般分有返回和不返回值的用法, 例如函数有返回值, 过程没有返回值.
在Ruby的方法中没有强制要求返回或不返回.
当没有写return时, 返回的是最后一个表达式的值.
当没有任何表达式时返回的是nil.
def method1
    a = 1
    b = 2
    c = a + b   # returns 3
end
def method2
    a = 1
    b = 2
    c = a + b
    return b   # returns 2
end
def method3
   "hello"   # returns "hello"
end
def method4
   a = 1 + 2
   "goodbye"   # returns "goodbye"
end
def method5
end   # returns nil

注意如果最后不是表达式, 是定义方法返回什么呢 ?
class Myclass
end
ob = Myclass.new
class << ob
  def method1
    def method2
      return 1
    end
  end
  def method3
    lambda{|x| puts(x)}
  end
end
p ob.method1
p ob.method2
p ob.method3

执行结果 : 
nil
1
#<Proc:0x1eb0bc8@C:/Users/digoal/Desktop/new.rb:11 (lambda)>

3. Returning Multiple Values
返回多个值, 在很多编程语言中, 一般是通过返回指针(或reference)来达到这个目的. 而不是返回这些值的拷贝.
Ruby在reference和value没有严格的界定. 所以不能这么做来返回多值(特殊情况例外).
Ruby 返回多值比较简单, 返回的值用逗号隔开就是了. 这样写的返回类型是Array.
当然你也可把需要返回的值存入Array再返回, 或者存入Hash, Set等类型再返回.
def ret_things
    greeting = "Hello world"
    a = 1
    b = 2.0
    return a, b, 3, "four", greeting, 6 * 10
     # 也可以这么写
    # z = a, b, 3, "four", greeting, 6 * 10
    # return z
end

p ret_things
p ret_things.class

执行结果 :
[1, 2.0, 3, "four", "Hello world", 60]
Array

下面的则是以Hash返回多值的举例 : 
def ret_hash
    return {'a'=>'hello', 'b'=>'goodbye', 'c'=>'fare thee well'}
end

p ret_hash
p ret_hash.class

返回 :
{"a"=>"hello", "b"=>"goodbye", "c"=>"fare thee well"}
Hash

4. Default and Unpredictable Arguments
4.1 书写规则 : 
Darg表示有默认值的参数, Arg表示没有默认值的参数, Uarg表示可选参数.
它们的书写规则  : 
(Darg,...)
(Arg,...)
(Uarg)
(Darg,...,Arg,...)
(Arg,...,Darg,...)
(Darg,...,Uarg)
(Arg,...,Uarg,Arg,...)
(Uarg,Arg,...)
(Arg,...,Uarg)
(Darg,...,Uarg,Arg,...)
(Arg,...,Darg,...,Uarg)
传入的参数个数必须满足 : 
>=Arg , <=(Arg+Darg+Uarg),  Uarg>=0
只有当传入的参数个数达到 Arg+Darg之后的值才会填充给Uarg.
传入的参数个数达到 Arg+Darg之后, 填充的值按方法定义中参数的顺序传递给对应的参数.
def method1(a=1,b=2,*c,d,e)
  return a,b,c,d,e
end
p method1(3,4)
p method1(3,4,5)
p method1(3,4,5,6)
p method1(3,4,5,6,7)
p method1(3,4,5,6,7,8)
执行结果 : 
[1, 2, [], 3, 4]
[3, 2, [], 4, 5]
[3, 4, [], 5, 6]
[3, 4, [5], 6, 7]
[3, 4, [5, 6], 7, 8]

默认值可以是个常量也可以是个变量甚至表达式, 例如 :
def fib(n, t = n < 2 ? n : fib(n-1) + fib(n-2)) 
  p t
end
p fib(4)
返回 : 
1
0
1
1
2
1
0
1
3
3


5. Assignment and Parameter Passing
方法有两个访问点, 一个是入口即参数传递.
另一个是出口即返回值.
那么参数传递进去后, 在方法内部参数会不会被修改呢?
除非是使用了!结尾的方法或者字符串类型的<<方法, 否则是不会影响到传入的参数的源值的.
def change( x )
    x += 1
    return x
end
num = 10
puts( "num.object_id=#{num.object_id}" )
change( num )
p num
puts( "num.object_id=#{num.object_id}" )

执行结果 :
num.object_id=21
10
num.object_id=21

来看看一个会修改源的例子 : 
def change(x)
  x << " def"
  x = x + " abc"
end
s1 = "hello"
p change(s1)
p s1

输出结果 :
"hello def abc"
"hello def"  # x 变成这个了, 为什么不是hello def abc呢, 原因是方法中的第二个表达式是赋值, 左边的x实际上是在这个方法内新产生的一个对象. 而不是右边的x了.

如果把上面的方法两句话颠倒会怎么样呢?答案是外面的x不会被修改, 修改的是新产生的对象.
def change(x)
  x = x + " abc"  # 产生一个对象
  x << " def"  # 修改的是新产生的对象
end
s1 = "hello"
p change(s1)
p s1

执行结果 :
"hello abc def"
"hello"

接下来再看一个不改变传入值, 直接输出的例子,
def nochange(x)
  # x = x  # 这行有没有都不影响结果
  return x
end
s1 = 'hello world'
s2 = s1
s3 = nochange(s1)
p s1.object_id
p s2.object_id
p s3.object_id
p nochange(s1).object_id

执行结果 :
16025784
16025784
16025784
16025784

6. object_id, Integers Are Special
上面的最后一个例子说明了一个问题, 在前面的章节也多次提到,  两个变量的object_id相同的话可以理解为这两个变量指向同一个"内存区域".
变量赋予给变量的时候, 其实是赋予给它这个指向的地址, 而不是重新拷贝一份数值到新的内存区域, 所以变量赋予给变量, 结果是两个变量的的object_id相等.
但是如果是直接赋值的话情况就不一样了, (Fixnum是一个特例, 值相同的变量object_id就相同).
s1 = 'hello'
s2 = 'hello'
s3 = s1

p 'hello'.object_id
p s1.object_id
p s2.object_id
p s3.object_id

i1 = 10
i2 = 10
i3 = i1

p 10.object_id
p i1.object_id
p i2.object_id
p i3.object_id

执行结果 :
15892308
15892332
15892320
15892332
21
21
21
21

7. One-Way-In One-Way-Out Principle
首先引用封装的解释 : 
encapsulation and information hiding are fundamental principles: 
If you send a variable x to a method y and the value of x is changed inside y, 
you cannot obtain the changed value of x from outside the method—unless 
the method explicitly returns that value.
Ruby语言严格遵循了这个原理.

8. Modifying Receivers and Yielding New Objects
来看个例子先 :
def change(x)
  x = x
  x << x
end

s1 = 'hello'
change(s1)
p s1

执行结果 : 
"hellohello"

如果把change(x)方法里面的两行改成以下任意一种, 结果和上面是一样的  :
  y = x
  y << x


  x << x

来分析一下就知道为什么外部的x会被修改了, 
首先, x = x 左边的x还是和右边的x一样指向同一个"内存区域". << 方法的后果是左边的x的original值会比修改.
y = x也和上面的解释一样.
注意 : 尽量不要在方法中改变传入对象的original值, 这样会打破第七点提到的封装原理.

那么怎样才能不导致original值被修改呢, 还记得clone方法吗, 以前多次提到的.
def change(x)
  x.clone << x   # x.clone新建了一个String object. 所以<<改变的是新建的object
end

s1 = 'hello'
change(s1)
p s1

执行结果 :
"hello"

再加一个书上的例子 : 
str1 = "hello"          #object_id = 23033940
str2 = "world"          #object_id = 23033928
str3 = "goodbye"        #object_id = 23033916
str3 = str2 << str1
puts( str1.object_id )  #=> 23033940 # unchanged
puts( str2.object_id )  #=> 23033928 # unchanged
puts( str3.object_id )  #=> 23033928 # now the same as str2!

9. Potential Side Effects of Reliance On Argument Values
在第8点里举过例子, 调用方法的时候可能修改传给这个方法的参数的original值.
这种方法是不可取的, 可能导致你的程序在调用这些方法后发生意想不到的事情.
注意这里说的是改变传入参数的original值, 而不是这个方法的对象的值.
会改变方法所属对象的值的方法如<<, downcase!, reverse!, capitalize! 等. 它们改变的是这个方法的所属对象, 而不是传入这个方法的参数. 这个是值得注意的.
以下方法是不可取的, 因为它们修改了传入参数的original值.
def stringProcess( aStr, anotherStr )
    myStr = aStr.capitalize!
    anotherStr.reverse!.capitalize!    
    myStr = myStr + " " + anotherStr.reverse
    return myStr
end
str1 = "hello"
str2 = "world"
str3 = stringProcess( str1, str2 )
puts( "#{str3}" )             #=> Hello worlD
puts( "#{str1} #{str2}" )     #=> Hello Dlrow

如果真的有可能需要直接操作传入的参数, 可以考虑使用clone方法拷贝并新增一个对象. 在新增的对象上操作.

10. Parallel Assignment
前面讲了方法可以return multiple values. 那么怎么将这些返回值赋予给多个变量呢?
其实用逗号隔开就可以了 :
s1,s2,s3,s4 = 1,"digoal",[1,2,3]
p s1
p s2
p s3
p s4  # 不足的以nil填充
s1,s2 = s2,s1  # 值交换, 不需要中间变量.
p s1
p s2
def method1(a=10,b=9,*c)
  return a,b,c
end
n1,n2,n3,n4 = method1(1,2,3,4,5,6)
p n1
p n2
p n3  # c是一个subArray
p n4  # 所以n4只能以nil填充

执行结果 : 
1
"digoal"
[1, 2, 3]
nil
"digoal"
1
1
2
[3, 4, 5, 6]
nil

11. By reference or By value?
在Ruby程序中调用方法传入的参数到底是以reference形式还是value形式传递的呢? 看个例子就明白了.
def aMethod( anArg )
    puts( "#{anArg.object_id}\n\n" )
end
class MyClass
end
i = 10
f = 10.5
s = "hello world"
ob = MyClass.new
puts( "#{i}.object_id = #{i.object_id}" )
aMethod( i )
puts( "#{f}.object_id = #{f.object_id}" )
aMethod( f )
puts( "#{s}.object_id = #{s.object_id}" )
aMethod( s )
puts( "#{ob}.object_id = #{ob.object_id}" )
aMethod( ob )

执行结果 :
10.object_id = 21
21

10.5.object_id = 16058796
16058796

hello world.object_id = 16057152
16057152

#<MyClass:0x1ea0668>.object_id = 16057140
16057140

说明Ruby是以reference的形式传递参数的. 
因为参数的object_id在方法内部和外部是一样的.
那么如果要以value的形式传递应该怎么做呢? 这个应该交给Ruby的解释器去做, 要么我们可以使用参数对象的clone方法, 传递进去. 例如 :
def method1(x)
end

s1 = "hello i'm digoal"
method1(s1.clone)


12. Are Assignments Copies or References?
赋值这个操作什么时候会生成新对象, 什么时候只是指向老的"内存区域"呢?
当等号右边是一个表达式或者一个值(不是变量)的时候, 会新生成对象. (Fixnum除外)
s1 = 'hello'
s2 = 'hello'
p s1.object_id
p s2.object_id

i1 = 10
i2 = (i1-10+10)
p i1.object_id
p i2.object_id

执行结果 :
15436092
15436080
21
21

当等号右边是一个变量的时候, 左边的对象指向右边的对象指向的"内存区域".
s1 = 'hello'
s2 = s1
p s1.object_id
p s2.object_id

i1 = 10
i2 = i1
p i1.object_id
p i2.object_id

执行结果 :
15600024
15600024
21
21

13. Tests for Equality : == or equal? method
先来看看==和equal?的区别 : 
Ruby Study 9 : Passing Arguments and Returning Values - 德哥@Digoal - The Heart,The World.
 BasicObject的 ==方法原本和equal?方法一样, 都用于比较两个对象是否相等. 它们是比较两个对象的object_id是否相等来判断它们是否相等的.
但是==方法往往被subclass修改(复写) , 不是比较object_id而是比较对象存储的内容. 例如String的==方法比较的是对象的内容, 而不是object_id.
equal?方法不会被任何subclass修改, 所以它可以用来比较两个对象的object_id. 判断它们是否指向同一个内存区域.
来看个例子 :
p 'abc'.equal?('abc')
p 'abc' == 'abc'

执行结果 : 
false
true

所有类中, Fixnum属于特例 : 
p 10.equal?(10)
p 10 == 10

执行结果 : 
true
true


【参考】
The Book Of Ruby
Ruby 1.9.3 API
目录
相关文章
|
移动开发 关系型数据库 Ruby