一、心得体会
1、完成了什么?
- 看了20页镐头书
- 看了10个controller
2、收获了什么?
- sub与gsub的区别(都是查找能匹配第一个参数的那部分字符串,同时用第二个参数替换它们,sub执行替换一次,gsub每次出现时都进行替换)
- 替换中的反斜线
- 面向对象的正则表达式
- preload 允许预先加载参数,与包含的相同
- 取消原因、分类、城市、颜色
- 投诉、投诉原因
- 咨询、顾客日志
- 订单优惠(coupe)
- 顾客(customer)
二、读书笔记
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