Day9 读书笔记&心得体会

2017-06-01  本文已影响17人  柳辉

一、心得体会
1、完成了什么?

2、收获了什么?

二、读书笔记
7.2 命令展开(Command Expansion)
7.3 赋值 (Assignment)
到目前为止,本书给出的每个例子几乎都有赋值语句的影子,或许到该说说它的时候了。

赋值语句将左侧的变量或者属性(左值)设置为右侧的值(右值),然后返回该值作为赋值表达式的结果,这意味着你可以链接赋值语句,并可以在某些特殊的地方执行赋值语句。

a = b = 1 + 2 + 3
a -> 6
b -> 6
a = (b = 1 + 2) + 3
a -> 6
b ->3
File.open(name = gets.chomp)

Ruby的赋值语句有两种基本形式,第一种是将一个对象引用赋值给变量或者常量。
这种形式的赋值在Ruby语言中是直接执行的(hardwired)。

instrument = "piano"
MIDDLE_A = 440

第二种形式等号左边是对象属性或者元素的引用。

song.duration = 234
instrument["ano"] = "CCOLO"

这种形式的特殊之处在于,它是通过调用左值的方法来实现的,这意味着你可以重载它们。

我们已经看了如何定义一个可写的对象属性:只要简单地定义一个以等于号结尾的方法即可。这个方法以其右值作为它的参数。

class song
  def duration = (new_duration)
    @duration = new_duration
  end
end

这种设置属性的方法不必和内部的实例变量相对应,并且具有赋值方法的属性也并非必须要有读取该属性的方法。

class Amplifier
  def volume = (new_volume)
    self.left_channel = self.right_channel = new_volume
  end
end

在老版本的Ruby中,赋值语句的返回值是设置该属性的方法的返回值,在Ruby1.8中,赋值语句的值总是参数的值而方法的返回值将被丢掉。

class Test
  def val=(val)
    @val = val
    return 99
  end
end
t = Test.new
a = t.val = 2
a -> 2

在老版本中,a将被赋值语句设置为99,而在Ruby1.8中,它的值是2。

7.3.1 并行赋值(Parallel Assignment)
举个栗子:

int a = 1;
int b =2;
int temp;
temp = a;
a = b;
b = temp;

在Ruby中你可以用下面的简洁的代码来实现。

a, b = b, a

Ruby的赋值实际上是以并行的方式执行的,所以赋值语句右边的值不受赋值语句本身的影响,在左边ed任意一个变量或者属性被赋值之前,右边的值按它们出现的顺序被计算出来。下面这个人为设计的例子说明了这一点。

第二行将表达式x,x+=1和x+=1的值分别赋值给变量a、b和c。

x = 0
a, b, c = x, (x +=1), (x +=1)

当赋值语句有多于一个左值时,赋值表达式将返回由右值组成的数组。如果赋值语句的左值多于右值,那么多余的左值将被忽略,如果右值多于左值,那么额外的右值将被忽略。如果赋值语句仅有一个左值而多个右值,那么右值将被转换成数组,然后赋值给左值。

使用Ruby的并行赋值操作,你可以叠起来和展开数组,如果最后一个左值有一个""前缀,那么所有多余的右值将被集合在一起,并作为一个数组赋值给左值。同样的,如果最后一个右值是一个数组,你可以在它的前面加一个"",它将被适当地展开成其元素的值(如果右边只有一个值,那么这就没有必要了——数组会自动展开的)。

嵌套赋值

并行赋值还有一个值得一提的特性:赋值语句的左边可以含有一个由括号括起来的变量列表。Ruby视这些变量为嵌套赋值语句,在处理更高层级的赋值语句前,Ruby会提取出对应的右值,并赋值给括起来的变量。

7.3.2 赋值语句的其他形式(Other Forms of Assignment)

和许多其他语言一样,Ruby有一个句法的快捷方式:a = a + 2可以写成a+=2。
内部处理时,会将第二种形式县转换成第一种形式,这意味着在你自己类中作为方法定义的操作符,和你预期的效果一样的。

class Bowdlerize
  def initialize(string)
    @value = string.gsub(/[aeiou]/, '*')
  end
  def +(other)
    Bowdlerize.new(self.to_s + other.to_s)
  end
  def to_s
    @value
  end
end

Ruby不支持C或Java中的自加(++)和自减(--)运算符,如果想用,使用 +=和-=替代之。

7.4 条件执行(Conditional Execution)
Ruby对条件代码的执行有几种不同的机制:大多数的形式比较常见,并且对许多形式做了一些简化,不过我们在学习它们之前,我们还是需要多花点时间来看看布尔表达式。

7.4.1 布尔表达式(Boolean Expression)
Ruby对“真值”(truth)的定义很简单:任何不是nil或者常量false的值都为真,那你会发现Ruby库程序常常利用这已事实。例如IO#gets方法返回文件的下一行,如果遇到文件尾则返回nil,利用它可以写出如下循环。

while line = gets
#process line
end

然而,C,C++和Perl程序员可能会落入陷阱:数字0不被解释为假值,长度为0的字符串也不是假值。这可是一个难以克服的顽固的习惯。

Defined?、与、或和非

Ruby支持所有标准的布尔操作符,并引入一个新操作符defined?。

仅当and和&&的两个操作数为真时结果才为真。并且仅当第一个操作数为真时才求解第二个操作数的值(这也称为短路求解,shortcircuit evaluation)。这两种形式的唯一区别在于优先级不同(and低于&&)。

同样,如果有一个操作数为真,那么or和||的结果为真,且仅当第一个操作数为假时才求解第二个操作数,和and一样,or和||的唯一区别是它们的优先级。
需要注意的是and和or有相同的优先级,而&&的优先级高于||。
not和!返回它们的操作数的相反值(如果操作数为真,则返回假;如果操作数为假,则返回真)。你可能已经想到了,是的,not和!的唯一区别是优先级不同。

如果参数未被定义,defined?操作符返回nil;否则返回对参数的一个描述,如果参数是yield,而且有一个block和当前上下文相关联,那么defined?返回字符串"yield"。

defined?1 -> expression
defined?dummy -> nil
defined?printf -> "method"
defined?String -> "constant"
defined?$_ -> "global-variable"
defined?Math::PI -> "constant"
defined?a = 1 -> "assignment"
defined?42.abs -> "method"

除了布尔表达式,Ruby对象还支持如下比较方法:==, ===,<=>,=~,eql?和equal?。除了<=>,其他方法都是在类object中定义的,但是经常被子类重载以提供适当的语义。例如,类Array重定义了==:当两个数组对象有相同的元素个数,且对应的元素也都相等时,才认为它们相等。

==和=都有相反的形式:!=和!。不过,在Ruby读取程序的时候,会对它们进行转换:a!=b等价于!(a=b); a !~b等价于!(a =b)。这意味着如果你写的类重载了==或者=,那么也会自动得到!=和!~。

你还可以用Ruby的range作为布尔表达式,像exp1..exp2这样的range,在exp1变为真之前,它的值为假;在exp2变为真之前,range被求解为真。一旦,exp2变为真,range将重置,准备再次重新计算。

Ruby1.8之前的版本中,可以用裸正则表达式(bare regular expression)作为布尔表达式,现在这种方式已经过时了,不过你仍然可以用~操作符将$_和一个模式进行匹配。

7.4.2 逻辑表达式的值(The Value of Logical Expression)

在上面,我们说“如果两个操作数都为真,and语句的值为真。”但实际上比这更微妙。操作符and,or,&&和||实际上返回首个决定条件真伪的参数的值。听着很复杂,那到底是什么意思?

比方说,表达式"vall and val2",如果val1为假或者nil,我们知道这个表达式不可能为真。在这个例子中,vall的值决定了表达式的值,所以返回它的值,如果vall为其他值,那么表达式的值依赖于val2,所以返回它的值。

nil and true -> nil
false and true ->false
99 and false -> false
99 and nil -> nil
99 and "cat" -> "cat"

注意:不管怎样,表达式的最终值是正确的。

or的值的计算与此类似(除了当vall的值为真时,才能提前知道or表达式的值)。

false or nil -> nil
nil or false -> false
99 or false -> 99

Ruby的一个惯用技法利用了这一特性。

words[key] ||= []
word[key] << word

第一行等价于words[key] = words[key] || []。如果散列表words中key对应的项没有值(nil),那么||的值是第二个操作数:一个新的空数组。这样,这两行代码将会把一个数组赋值给没有值的散列表元素,对已经有值的元素原封不动。有时,你也会看到它们写到一行的情况:

(word[key] ||=[]) << word

7.4.3 If和Unless表达式(If and Unless Expressions)
Ruby的if表达式和其他语言的“if”语句非常类似。

if song.artist == "Gillespie" then
  handle = "Dizzy"
elseif song.artist == "Parker" then
  handle = "Bird"
else
  handle = "unkown"
 end

如果将if语句分布到多行上,那么可以不用then关键字。

if song.artist == "Gillespie"
  handle = "Dizzy"
elseif song.artist == "Parker"
  handle = "Bird"
else
  handle = "unkown"
end

不过,如果你想让你的代码更紧凑些,你可以使用then关键字来区分布尔表达式和它后面的语句。

if song.artist == "Gilleapie" then handle = "Dizzy"
elseif song.artist == "Parker" then handle = "Bird"
else handle = "unkown"
end

使用冒号(:)来替代then可以使得代码更简洁。

if song.artist == "Gillespie": handle = "Dizzy"
elseif song.artist == "Parker": handle = "Bird"
else handle = "unknown"
end

你可以用零个或者多个elseif从句和一个可选的else从句。

正如我们前面所说,if是表达式而不是语句——它返回一个值,你不必非要使用if表达式的值,但它迟早能派上用场。

handle = if song/artist == "Gillespie" then "Dizzy"
elseif song.artist == "Parker" then
"Bird"
else
"unknown"
end

Ruby还有一个否定形式的if语句。

unless song.duration > 180
  cost = 0.25
else
  cost = 0.35
end

最后,给C的爱好者一个惊喜,Ruby也支持C风格的条件表达式。

cost = song.duration > 180 ? 0.35 : 0.25

条件表达式返回的是冒号前面表达式的值还是冒号后面表达式的值,依赖于问好前面布尔表达式值的真伪。在这个例子中,如果歌曲的长度大于3分钟,则表达式返回0.35。不管结果,是什么,它将被赋给cost变量。

if和unless修饰符

Ruby和Perl都有一个灵活的特性:语句修饰符允许将条件语句附加到常规语句的尾部。

mon, day, year = $1, $2, $3, if date =~ /(\d\d)-(\d\d)-(\d\d)/
puts "a = #{a}" if debug
print total unless total.zero?

对于if修饰符,仅当条件为真时才计算前面的表达式的值,unless与此相反。

File.foreach("/etc/fstab") do |line|
  next if line =~ /^#/
  parse(line) unless line =~ /^$/
end

因为if本身也是一个表达式,下面的用法会让人感到晦涩难懂:

if artist == "John Co"
  artist = "'tr"
end unless use_nickname == "no"

7.5 Case表达式 (Case Expression)
Ruby的case表达式非常强大:它相当于多路的if。而它有两种形式,使得它更加强大。

第一种形式接近于一组连续的if语句:它让你列出一组条件,并执行第一个为真的条件表达式所对应的语句。

leap = case
  when year % 400 == 0: true
  when year % 100 == 0: false
  when year % 4     == 0
end

case语句的第二种形势可能更常见。在case语句的顶部指定一个目标,而每个when从句列出一个或者多个比较条件。

case input_line
when "debug"
  dump_debug_info
  dumo_symbols
when /p\s+(\w)/
  dump_variable($1)
 when "quit", "exit"
  exit
else
  print "Illegal command: #{input_line}"
end

和if一样,case返回执行的最后一个表达式的值;而且如果表达式和条件在同一行的话,你可以用then关键字来加以区分。

kind = case year
  when 1850..1889 then "Blues"
  when 1890..1909 then "Ragtime"
  when 1910..1929 then "New Orleans Jazz"
  when 1930..1939 then "Swing"
  when 1940..1950 then "Bebop"
else                                 "Jazz"
end

和if语句一样,也可以使用冒号(:)来替代then关键字。

kind = case year
  when 1850..1889 :  "Blues"
  when 1890..1909 :  "Ragtime"
  when 1910..1929 :  "New Orleans Jazz"
  when 1930..1939 :  "Swing"
  when 1940..1950 :  "Bebop"
else                                 "Jazz"
end

case通过比较目标(case关键字后面的表达式)和when关键字后面的比较表达式来运作。这个测试通过使用comparison === target来完成。只要一个类为 ===(内建的类都有)提供了有意义的语义。那么,该类的对象就可以在case表达式中使用。

例如,正则表达式将===定义为一个简单的模式匹配。

case line
when /title=(.*)/
  puts "Title is #$1"
when /track=(.*)/
  puts "Track is #$1"
when /artist=(.*)/
  puts "Artist is #$1"
end

Ruby的所有类都是类Class的实例,它定义了==以测试参数是否为该类或者其弗雷的一个实例。所以,放弃了多态的好处,并把重构的“福音”带到你的耳侧,你可以测试对象到底属于哪个类。

case shape
when Square, Rectangle
# ..
when Circle
# ..
when Triangle
# ..
end

7.6 循环
不要告诉任何人,Ruby内建的循环结构相当原始。

只要条件为真,while循环就会执行循环体。比如,下面这个常用法读取输入直到输入结束。

while line = gets
  # ...
end

until循环与此相反,它执行循环体直到循环条件变为真。

until play_list.duration > 60
  play_list.add(song_list.pop)
end

与if与unless一样,你也可以用这两种循环体做语句的修饰符。

a = 1
a *= 2 while a <100
a -= 10 until a < 100
a -> 98

我们之前说过range可以作为某种后空翻:当某个事件发生时变为真,并在第二个事件发生之前一直保持为真。这常被用在循环中。在下面的例子中,我们读一个含有前十个序数(first,second等等)的文本文件,但只输出匹配“third”和“fifth”之间的行。

file = File.open("ordinal")
while line = file.gets
  puts(line) if line =~ /third/ .. line =~ /fifth/
end

输出结果:

third
fourth
fifth

你可能会发现熟悉的Perl的人,它们的写法和前面的代码稍有不同。

file = File.open("ordinal")
while file.gets
  print if ~/third/ .. ~/fifth/
end

这段代码背后有点小戏法:gets将读取的最后一行赋值给全局变量$, ~操作符对$执行正则表达式匹配,而不带参数的print将输出$_。在Ruby社区中,这种类型的额代码已经过时了。

用在布尔表达式中的range的起点和终点本身也可以是表达式,每次求解总体布尔表达式时就会求解起点和终点表达式的值。

例如,下面的代码利用了变量$.包含当前输入行号这一事实,来显示1到3行以及位于/eig/和/nin/之间的行。

File.foreach("ordinal") do |line|
  if (($. == 1) || line =~ /eig/) .. (($. == 3) || line =~ /nin/)
    print line
  end
end

当使用while和until做语句修饰符时,有一点要注意:如果被修饰的语句是一个begin/end块,那么不管布尔表达式的值是什么,快内的代码至少会执行一次。

print "Hello\n" while false
begin
  print "Goodbye\n"
end while false

7.6.1 迭代器(lterators)
如果你读了前面小节的开头部分,当时可能会很失望,因为我们在那儿提到:“Ruby内建的循环结构很原始”。

举个栗子,Ruby没有"for"循环——至少没有类似与C,C++和Java中的for循环。但是Ruby使用各种内建的类中定义的方法来提供类似健壮的功能。

让我们看几个例子。

3.times do
  print "Ho!"
end

这种代码易于避免长度错误和差异:这种循环会执行3次,除了函数,通过调用downto和upto函数,整数还可以在指定的range循环,而且数字都可以使用step来循环。例如,传统的从0到9的”for“循环(类似i=0; i<10; i++)可以写成:

0.upto(9) do |x|
  print x, " "
end

从0到12,步长为3的循环可以写成:

0.step(12, 3) {|x| print x, " "}

类似的,使用each方法使得遍历数组和其他容器变得十分简单。

[1, 1, 2, 3, 5].each {|value| print val, " "}

并且如果一个类支持each方法,那么也会自动支持Enumerable模块中的方法,例如,File类提供了each方法,它依次返回文件中的行,使用Enumerable中的grep方法,我们可以只迭代那些满足某些条件的行:

File.open("Ordinal").grep(/d$/) do |line|
  puts line
end

最后也可能是最简单的:Ruby提供了一个内建的称为loop的迭代器。

loop do 
# block
end

loop循环一直执行给定的块(或者直到跳出循环,后面会降到如何做)。

7.6.2 For ... In

前面我们说到Ruby内建的循环原语只有while和until。那么for呢?是的,for基本上是一个语法块,当我们写:

for song in songlist
  song.play
end

Ruby把它转成:

songlist.each do |song|
  song.play
end

for循环和each形式的唯一区别是循环体中局部变量的作用域。

你可以使用for去迭代任意支持each方法的类,例如Array或者Range。

for i in ['fee', 'fi', 'fo', 'fum']
  print i, " "
end

for i in 1..3
  print i, " "
end

for i in File.open("ordinal").find_all {|line| line =~ /d$/}
  print i.chomp, " "
end

只要你的类支持each方法,你就可以使用for循环去遍历它的对象。

class Periods
  def each
    yield "Classical"
    yield "Jazz"
    yield "Rock"
  end
end

periods = Periods.new
for genre in periods
  print genre, ""
end
上一篇下一篇

猜你喜欢

热点阅读