Oh!Coder

Coding Life

读《Ruby编程语言》笔记(二)

| Comments

继续阅读。

这次读了第四章和第五章,做了一些摘录,每条摘录依然标出了原书中的页码数,记录如下。

一、Ruby的变量类型(P.87)

  • Ruby中有四种类型的变量,而且由词法规则来规范它们的名字。以$开头的变量是全局变量,在整个Ruby程序里都是可见的。以@@@开头的变量分别是实例变量和类变量,用于面向对象变成。以一个下划线或小写字符开头的变量是局部变量,它们仅在当前方法或代码块内有定义。

二、常量(P.88)

  • 按照惯例,大多数常量名都是全部大写的,并且使用下划线来分隔单词,比如LIKE_THIS。Ruby的类和模块名也是常量,但依照惯例,它们采用驼峰格式(camel case),比如LikeThis
  • 尽管常量看起来就好像首字母大写的局部变量一样,它们却具有全局的可见性:可以在一个Ruby程序的任何地方使用它们而不必考虑作用域。但是与全局变量不同的是,常量可以在类和模块中被定义,因此可以拥有限定修饰的名字。
  • 一个常量引用是一个表达式,它的值就是该常量的值。

#### 三、全局函数(P.90)

  • 在Kernel中定义的方法是全局函数,就好比是在所有类之外的顶层空间中定义的方法。全局函数被作为Object类的私有方法来定义的。

四、数组的访问(P.91)

  • Ruby解释器将对数组的访问转换成下面这种形式:a.\[ ](0),对数组的访问变成了一个对数组的\[ ]方法的调用,并且将数组索引作为实参。这种数组访问语法并不仅限于数组,任何对象都可以定义一个\[ ]方法。当使用方括号来“索引”该对象时,任何位于方括号中的值都会被作为实参传递给\[ ]方法。

五、常量赋值(P.94)

  • 对于一个已经存在的常量进行赋值会导致Ruby发出一个警告,然而Ruby仍然会执行这个赋值,这意味着常量并不是真正恒久不变的。
  • 在方法体内部是不允许对常量进行赋值的。
  • 与变量不同,只有在Ruby解释器真正地执行了对常量的赋值表达式之后,常量才会存在。这就意味着常量绝对不会处于未初始化的状态。如果一个常量已经存在,那么它必然会有一个被赋给它的值。只有将nil赋给一个常量时,它才会具有nil值。

六、展开操作符(P.98)

  • 如果一个右值以\*开头,那么就意味着它是一个数组(或一个类似于数组的对象),而且它的每个元素都应该是一个右值。首先数组的元素会替换该数组在原右值列表中的位置,然后进行赋值操作:x, y, z = 1, *[2,3] # Same as x,y,z = 1, 2, 3
  • 当在一个左值前放置一个\*时,意味着所有多出来的右值都会被放入一个数组并且赋给该左值。赋给该左值的值总是一个数组,它可以包含零个,一个或多个元素:
  • x, *y = 1, 2, 3 # x=1, y=[2,3]
  • x, *y = 1, 2 # x=1, y=[2]
  • x, *y = 1 # x=1, y=[ ]

七、并行赋值中的圆括号(P.99~P.100)

  • 如果将两个或多个左值放在一对圆括号里构成一个组,那么它将被作为一个左值来处理。一旦确定了和它对应的右值,它就会递归地应用并行赋值的规则——那些右值被赋给那些位于圆括号中的左值。例如:x, (y,z) = a, b # x=a; y,z = b
  • 下面这些具体的例子能更清晰地展示这个特性。请注意,左侧的圆括号会“拆开(unpack)”右侧的嵌套数组中与其对应的那一层:
  • x,y,z = 1,[2,3] # No parents: x=1;y=[2,3];z=nil
  • x,(y,z) = 1, [2,3] # Parents: x=1;y=2;z=3
  • a,b,c,d = [1,[2,[3,4]]] # No parents: a=1;b=[2,[3,4]];c=d=nil
  • a,(b,(c,d)) = [1,[2,[3,4]]] # Parents: a=1;b=2;c=3;d=4

八、if修饰符(P.121~P.122)

  • if修饰符的优先级非常低,它与操作数绑定的紧密程度还不如赋值操作符,所以当使用if修饰符时,请确认你确实修饰了想要修饰的表达式。
  • 比如,下面的两行代码是不一样的:
  • y = x.invert if x.respond_to? :invert
  • y = (x.invert if x.respond_to? :invert)
  • 在第一行代码里,if修饰符作用于整个赋值表达式。如果x没有名为invert的方法,那么不会有任何事情发生。在第二行代码里,if修饰符仅仅作用于方法调用。如果if没有一个invert方法,那么该修饰符表达式的值将为nil,然后这个值被赋给y

九、case语句(P.124)

  • case语句中的一个when从句可以包含多个表达式(用逗号分隔),只要这些表达式中有一个为true,那么与该when从句相关联的代码就被执行。在简单形式的case语句里,逗号的作用不大,就好像一个||操作符一样。例如:
  • case when x == 1, y == 0 then "x is one or y is zero" # Obscure syntax when x == 2 || y == 1 then "x is two or y is one" # Easier to understand end

十、selectreject以及inject(P.133)

  • select方法为那个调用它的可枚举对象的每个元素执行相关联的代码块,如果代码块的返回值不是falsenil,那么该元素就会被选中,最后select将所有被选中的元素组合成一个数组并返回。例如:
    evens = (1..10).select{ |x| x%2 == 0 } # => [2, 4, 6, 8, 10]

  • reject方法和select方法恰好相反,它返回那些使代码块返回nilfalse的元素。例如:
    odds = (1..10).reject{ |x| x%2 == 0 } # => [1, 3, 5, 7, 9]

  • inject方法比其他的方法稍微复杂一些。它用两个参数调用相关联的代码块,第一个参数是一个来自此前的迭代的累计值,第二个参数则是那个调用它的可枚举对象的下一个元素。如果当前迭代不是最后一次迭代,那么其代码块的返回值就成为下一次迭代的第一个参数(即累计值),否则,就成为inject迭代器的返回值。例如:
    data = [2, 5, 3, 4] sum = data.inject{ |sum, x| sum + x } # => 14 (2+5+3+4) floatprod = data.inject(1.0) { |p, x| p \* x } # => 120.0 (1.0\*2\*5\*3\*4) max = data.inject { |m, x| m > x ? m : x } # => 5 (largest element)

十一、內部迭代器和外部迭代器的比较(P.137)

  • 当客户代码控制迭代时,该迭代器被称为外部迭代器,反之当迭代器控制迭代时,该迭代器就是一个内部迭代器。那些使用外部迭代器的客户代码必须负责推进整个遍历过程并且显示地从迭代器中获得下一个元素。相比之下,在使用内部迭代器时,客户代码将一个操作传递给一个内部迭代器,该迭代器依次在每个元素上应用该操作
  • 外部迭代器比内部迭代器更灵活。比如,使用外部迭代器可以很容易地比较两个集合的相等性,如果用内部迭代器则不太可能。但是从另一个角度来看,内部迭代器更容易使用,因为它们已经为你定义好了迭代逻辑。

十二、带一个值的break(P.148~P.149)

  • break语句可以为它所跳出的循环或迭代器指定一个值,也可以在break关键字后面加上一个表达式,或者一个逗号分隔的表达式列表。如果break后面没有表达式,那么循环表达式的值,或者迭代器方法的返回值就是nil。如果break后面有一个表达式,那么该表达式的值就成为循环表达式的值,或者成为迭代器的返回值。如果break后面有多个表达式,那么这些表达式的值会被放到一个数组里,该数组成为循环表达式的值,或者成为迭代器的返回值。
  • 与此相对,一个正常结束的、没有break语句的while循环的值总是nil。一个正常结束的迭代器返回值取决于该迭代器的定义,许多迭代器,比如timeseach,只是简单地返回那个调用它们的对象。

十三、next和代码块的值(P.150)

  • 当用在一个循环中时,next之后的任何值都会被忽略掉。但是当用在一个代码块中时,next之后的单个或多个表达式将成为那个调用该代码块的yield语句的“返回值”。如果next之后没有表达式,那么yield语句的值为nil。如果next之后有一个表达式,那么该表达式的值成为yield的值。如果next后面有一个表达是列表,那么yield的值就是一个由那些表达式的值所构成的数组。

十四、redo关键字(P.151)

  • redo语句重新开始一个循环或迭代器的当前迭代,这一点和next不同。next将控制权传递到一个循环或代码块的末尾,从而可以开始下一轮迭代,而redo则将控制权传递到循环或代码块的开头,从而可以重新开始当前迭代。redo将控制权传递给循环体或代码块的第一个表达式。它不会重新测试循环条件,也不会获取迭代器的下一个元素。

十五、rescue从句中的retry(P.161)

  • 当在一个rescue从句中使用retry语句时,它会重新运行该rescue从句所依附的那段代码。当一个异常是由一个短暂的故障引起的,比如一个超负荷的服务器,那么用再试一次的方式来处理该异常也许就是有意义的。

十六、else从句(P.162)

  • else从句是rescue从句的一个替代性选择,如果所有的rescue从句都不需要,那么就可以使用它。也就是说,如果begin语句中的代码执行完毕而且没有任何异常,那么else从句中的代码就被执行。
  • 请注意,在没有任何rescue从句的情况下使用else从句是没有什么意义的,Ruby解释器允许这种情况出现但是会发出警告。在else从句的后面不可以出现rescue从句。
  • 最后,只有在begin从句中的代码被执行完毕,而且控制流自然而然地地被转移到else从句中时,程序才会执行else从句。如果在执行begin从句的过程中发生了异常,那么显然else从句就不会再被执行。此外,begin从句中的breakreturnnext,以及类似的语句都可能阻止else从句的执行。

十七、ensure从句(P.162~P.163)

  • 无论begin从句中的代码发生了什么事,ensure从句中的代码都会被执行:

  • 如果代码正常地执行完毕,那么控制流转移到else从句——如果存在的话——然后转移到ensure从句。
  • 如果代码要执行一个return语句,那么在返回之前,控制流会跳过else从句,直接转移到ensure从句并执行其中的代码。
  • 如果begin从句的代码发生了异常,那么控制流转移到合适的rescue从句,然后会转移到ensure从句。
  • 如果没有rescue从句,或者没有能够处理该异常的rescue从句,那么控制流直接转移到ensure从句,在异常传播到外围代码块或沿着调用栈向上传递之前,先执行ensure从句中的代码。
  • 一个ensure从句可以取消一个异常的传播过程,只要它发起一些能够导致控制流转移的动作即可。如果一个ensure从句跑出了一个异常,那么新的异常传播过程将取代原有的。如果一个ensure从句包含一个return语句,那么异常传播被停止,而且包含它的方法将被返回。诸如breaknext之类的控制结构也具有类似的效果:停止当前的异常传播,转而进行指定的控制流转移。

Comments