下面是本书中示例代码会用到的其他特性。 1.6.1 局部变量和赋值 就像我们已经看到的那样,Ruby 仅允许通过赋值声明局部变量: >> greeting = 'hello' => "hello" >> greeting => "hello" 我们还可以通过数组一次给多个变量并行赋值: >> width, height, depth = [1000, 2250, 250] => [1000, 2250, 250] >> height => 2250 1.6.2 字符串插值 字符串可以使用单引号也可以使用双引号表示。对双引号中的字符串,Ruby 会自动用表达式的结果替换 #{ 表达式 },以执行字符串插值操作。 >> "hello #{'dlrow'.reverse}" => "hello world" 如果被插入的表达式返回的不是一个字符串类型的对象,那么这个对象就会自动收到一个to_s 消息以返回能顶替其位置的字符串。我们可以借此控制被替换对象的展示方式: >> o = Object.new => #<Object> >> def o.to_s 'a new object' end => nil >> "here is #{o}" => "here is a new object" 1.6.3 检查对象 每当 IRB 需要显示一个对象,类似下面的一些事情就会发生:向这个对象发送 inspect 消息,然后这个对象返回自身的字符串表示。Ruby 当中所有对象默认都有对 #inspect 的合理实现,但是通过提供自己的定义,我们就可以控制如何在控制台显示对象: >> o = Object.new => #<Object> >> def o.inspect '[my object]' end => nil >> o => [my object] 1.6.4 打印字符串 方法 #puts 对每个 Ruby 对象(包括 main)都可用,可以用来向标准输出打印字符串: >> x = 128 => 128 >> while x < 1000 puts "x is #{x}" x = x * 2 end x is 128 x is 256 x is 512 => nil 1.6.5 可变参数方法(variadic method) 定义方法时可以使用 * 运算符,以支持数目可变的参数: >> def join_with_commas(*words) words.join(', ') end => nil >> join_with_commas('one', 'two', 'three') => "one, two, three" 一个方法定义只能有一个可变参数,而常规参数放到可变参数的前后都可以: >> def join_with_commas(before, *words, after) before + words.join(', ') + after end => nil >> join_with_commas('Testing: ', 'one', 'two', 'three', '.') => "Testing: one, two, three." 在发送消息的时候,* 运算符还可以把每一个数组元素当作单个参数处理: >> arguments = ['Testing: ', 'one', 'two', 'three', '.'] => ["Testing: ", "one", "two", "three", "."] >> join_with_commas(*arguments) => "Testing: one, two, three." * 也可以使用并行赋值方式: >> before, *words, after = ['Testing: ', 'one', 'two', 'three', '.'] => ["Testing: ", "one", "two", "three", "."] >> before => "Testing: " >> words => ["one", "two", "three"] >> after => "." 1.6.6 代码块 代码块(block)是由 do/end 或者大括号围住的一段 Ruby 代码。方法可以带一个隐式代码块参数,并使用 yield 关键字表示对代码块中那段代码的调用: >> def do_three_times yield yield yield end => nil >> do_three_times { puts 'hello' } hello hello hello => nil 代码块可以带参数: >> def do_three_times yield('first') yield('second') yield('third') end => nil >> do_three_times { |n| puts "#{n}: hello" } first: hello second: hello third: hello => nil yield 返回执行代码块的结果: >> def number_names [yield('one'), yield('two'), yield('three')].join(', ') end => nil >> number_names { |name| name.upcase.reverse } => "ENO, OWT, EERHT" 1.6.7 枚举类型 Ruby 有 一个叫作 Enumerable 的 内置模块,被数组(Array)、散列表(Hash)、范围(Range)以及其他表示值的集合的类包含。Enumerable 提供的方法可以帮助我们对集合进行遍历、搜索和排序,其中的很多方法在调用时都可以带上一个代码块。通常,代码块中的代码会根据集合中的一些值或全部值来运行,以此承担方法的一部分工作。例如: >> (1..10).count { |number| number.even? } => 5 >> (1..10).select { |number| number.even? } => [2, 4, 6, 8, 10] >> (1..10).any? { |number| number < 8 } => true >> (1..10).all? { |number| number < 8 } => false >> (1..5).each do |number| if number.even? puts "#{number} is even" else puts "#{number} is odd" end end 1 is odd 2 is even 3 is odd 4 is even 5 is odd => 1..5 >> (1..10).map { |number| number * 3 } => [3, 6, 9, 12, 15, 18, 21, 24, 27, 30] 通常,一个代码块带有一个参数,并向此参数发送一个无参的消息,所以 Ruby 提供了一种缩写方式 &:message ,这比写代码块 { |object| object.message } 更为简洁: >> (1..10).select(&:even?) => [2, 4, 6, 8, 10] >> ['one', 'two', 'three'].map(&:upcase) => ["ONE", "TWO", "THREE"] 有的代码块可以为集合中的每个值生成一个数组,Enumerable 的方法 #flat_map 能把这些生成的结果数组连接起来: >> ['one', 'two', 'three'].map(&:chars) => [["o", "n", "e"], ["t", "w", "o"], ["t", "h", "r", "e", "e"]] >> ['one', 'two', 'three'].flat_map(&:chars) => ["o", "n", "e", "t", "w", "o", "t", "h", "r", "e", "e"] 还有一个有用的方法 #inject。有些代码块会处理集合中的每个值,#inject 能对这个代码块求值并累积成一个最终结果: >> (1..10).inject(0) { |result, number| result + number } => 55 >> (1..10).inject(1) { |result, number| result * number } => 3628800 >> ['one', 'two', 'three'].inject('Words:') { |result, word| "#{result} #{word}" } => "Words: one two three" 1.6.8 结构体 结构体(Struct)是 Ruby 中一个特殊的类,它的工作是生成其他类。根据传进 Struct.new 的每个属性名,Struct 产成的类会包含相应的获取方法和设置方法。要使用由结构体生成的类,常见方式是对其进行子类化;我们可以给子类起个名字,然后在里边定义其他任意的方法。例如,为了创建一个拥有属性 x 和 y,名字是 Point 的类,可以写成: class Point < Struct.new(:x, :y) def +(other_point) Point.new(x + other_point.x, y + other_point.y) end def inspect "#<Point (#{x}, #{y})>" end end 现在我们可以创建 Point 的一些实例,然后在 IRB 中进行检查,并给它们发送消息: >> a = Point.new(2, 3) => #<Point (2, 3)> >> b = Point.new(10, 20) => #<Point (10, 20)> >> a + b => #<Point (12, 23)> 和我们定义的所有方法一样,Point 实例会响应消息 x 和 x=,以便获取和设置属性 x 的值。 y 和 y= 与 x 和 x= 的情况类似: >> a.x => 2 >> a.x = 35 => 35 >> a + b => #<Point (45, 23)> 由 Struct.new 生成的类还有其他实用功能,像判断是否相等的方法 #== 的实现,就可以比较两个结构体的属性是否相等: >> Point.new(4, 5) == Point.new(4, 5) => true >> Point.new(4, 5) == Point.new(6, 7) => false 1.6.9 给内置对象扩展方法(Monkey Patching) 我们随时都可以给类或模块增加方法。这是一个强大的特性,通常叫作 Monkey Patching,可以让我们扩展已有类的行为: >> class Point def -(other_point) Point.new(x - other_point.x, y - other_point.y) end end => nil >> Point.new(10, 15) - Point.new(1, 1) => #<Point (9, 14)> 我们甚至可以扩展 Ruby 内置的类: >> class String def shout upcase + '!!!' end end => nil >> 'hello world'.shout => "HELLO WORLD!!!" 1.6.10 定义常量 Ruby 支持一种叫作常量的特殊变量。一般而言,常量一旦创建,就不能再被重新赋值。(Ruby 并不会阻止一个常量被重新赋值,但它会产生警告,以便我们知道自己做错了事。)任何以大写字母开头的变量都是常量。可以在顶层或者在一个类或模块中定义新的常量: >> NUMBERS = [4, 8, 15, 16, 23, 42] => [4, 8, 15, 16, 23, 42] >> class Greetings ENGLISH = 'hello' FRENCH = 'bonjour' GERMAN = 'guten Tag' end => "guten Tag" >> NUMBERS.last => 42 >> Greetings::FRENCH => "bonjour" 类和模块的名字总是以大写字母开头,所以类和模块的名字也是常量。 1.6.11 删除常量 在使用 IRB 进行探索时,如果我们想重新定义某个类或模块,而不是要扩展它们,实用的做法是让 Ruby 完全忽略该常量。一个顶层常量可以通过给 Object 发送消息 remove_const来删除,同时还要把常量名作为符号(symbol)对象传进去: >> NUMBERS.last => 42 >> Object.send(:remove_const, :NUMBERS) => [4, 8, 15, 16, 23, 42] >> NUMBERS.last NameError: uninitialized constant NUMBERS >> Greetings::GERMAN => "guten Tag" >> Object.send(:remove_const, :Greetings) => Greetings >> Greetings::GERMAN NameError: uninitialized constant Greetings 只能使用 Object.send(:remove_const, : 常量名 ) 而非 Object.remove_const(: 常量名 ),这是因为 remove_const 是一个私有(private)方法,只能通过从 Object 类的自身内部发送消息来调用;使用 Object.send 时,我们可以暂时跳过这个限制。
计算的本质——1.6 其他特性
书名: 计算的本质
作者: [英] Tom Stuart
出版社: 人民邮电出版社
原作名: Understanding Computation: From Simple Machines to Impossible Programs
副标题: 深入剖析程序和计算机
译者: 张伟
出版年: 2014-11
页数: 300
定价: 69.00元
装帧: 平装
ISBN: 9787115361547