08 November 2014

前言

虽然从事RoR工作快5个月了,老实说,ruby学的并不怎么样。工作时,要用到某个东西,都是百度上搜的,写代码主要靠猜和试,看起来相当的业余的,总不是个办法,决定系统的学习一下, 将原本零碎的知识点组织一下,形成体系。

杂记

哈希中的键名可以是符号,哈希的each用法:

ad =  {name: "邪王真眼", location: "Earth"}
ad.each do |k,v|
  puts "#{k}: #{v}"
end
``` `=~`是用来正则表达式匹配的,顺序可颠倒:/pattern/i =~ "String", `===`相等性判断的左值和右值不能随意变换,理解:操作符本质上是方法调用,左值是接受者。 ## 创建命令 从命令行中读取数据(以空格分隔),`ARGV数组`存放命令行参数。Ruby实现Unix的grep命令: ```ruby
# simple_grep.rb
# 使用方式: simple_grep.rb pattern filename
pattern = Regexp.new ARGV[0]
filename = ARGV[1]

file =  File.open(filename)
file.each_line do |line|    # 迭代器 + 正则表达式的用法还真是强大
  print line if pattern =~ line
end

file.close

稍微模块化的版本:

def simple_grep(pattern, filename)
  file =  File.open(filename)
  file.each_line do |line|
    print line if pattern =~ line
  end

  file.close
end

simple_grep Regexp.new(ARGV[0]), ARGV[1]

一直不知道grep命令使用方法: grep pattern filename。不过常用git grep和ack, 就使用经验而言,后两者要很多,应为存在默认的选项(默认当前工作区和默认当前目录)。

命名,变量类型:全局变量$,实例变量@,类变量@@(不太明白作用,难道是类方法)

多重赋值的优点:交换值只需要一行代码,使用数组给多个值赋值,组合变量名。

条件 - Ruby的谓词方法。逻辑运算符,case语句的示例:

tags = [ "A", "IMG" , "PRE" ]
tags.each do |tag|
  case tag
  when "P", "A", "I","B","BLOCKQUITE" # when语句可以是这样多条件,也可以是正则表达式
    p "#{tag} 是王八蛋"
  when "IMG", "BR"
    p "#{tag} 是牛顿"
  else
    p "邪王正眼,最强之眼!"
  end
end

问题: 上述这段代码在运行过程中出现了一点问题,invalid multibyte char (US-ASCII),而在irb中是正常的,解决方法是在文件首部添加# encoding: utf-8,并使用ruby case.rb而不是./case.rb这样的运行方式。

===操作符,左值为数值或字符串时,同==含义相同; 可以用作=~的作用,可以判断右边的对象是否属于左边的类,广义上的相等。

对象的同一性,object_id(__id__)相同。

循环语句: times, while, each, for, until, loop。循环控制:break, next, redo, 如下是循环控制例子:

puts "break example "
i = 0
["Perl", "Python", "Ruby", "Scheme"].each do |lang|
  i += 1
  break if i == 3
  p [i,lang]
end

p "next example "
i = 0
["Perl", "Python", "Ruby", "Scheme"].each do |lang|
  i += 1
  next if i == 3
  p [i,lang]
end

p "redo example "
i = 0
["Perl", "Python", "Ruby", "Scheme"].each do |lang|
  i += 1
  redo if i == 3
  p [i,lang]
end
# 输出结果:
# break example 
# [1, "Perl"]
# [2, "Python"]
# "next example "
# [1, "Perl"]
# [2, "Python"]
# [4, "Scheme"]
# "redo example "
# [1, "Perl"]
# [2, "Python"]
# [4, "Ruby"]
# [5, "Scheme"]

方法

调用方法: 向对象发送消息(message)。其形式有:带块的方法调用,运算符形式的方法调用(+, =~, -,!, [],[]=)。

方法的分类: 实例方法(接受者为对象),类方法(self.xxx,接收者为类),函数式方法(不需要接收者)

参数个数不确定的方法定义:

def foo(*args)
  args.class  # 在irb中测试了一下,发现args实际是个Array类型的数组
end

ruby 2.0支持所谓的关键字参数: def foo( key: value[,key: value]) 即可使用散列传参,未定义参数: **agrs参数接受任何未定义的参数。

判断对象所属类: instance_of? ; 判断类所属父类: is_a?

类的创建: class关键字,initialize方法(new委托的方法)。引用未初始化的instance varible返回为空。存取器: 对象外部不能直接访问或修改实例变量,attr_reader, attr_writer以及attr_accessor,自动生成存取访问器。类创建的示例:

class HelloWorld
  attr_accessor :name   # name对应潜在的@name实例变量,对象使用实例方法保存状态
  def initialize(myname = "Ruby")
    @name = myname
  end

  def greet
    puts "Hi, I am #{name}"  # #{}中允许放入任意复杂的表达式
  end

  def test_name
    name = "Ruby"      # 对局部变量name赋值
    self.name = "Ruby" # 调用name= 方法
  end
end

alice = HelloWorld.new("Alice")
ruby  = HelloWorld.new

ruby.greet

注意: 任何方法调用都有接收者,省略时默认为self。对于调用name=这样的方法,需要显式使用self。不能被自定义的变量名: nil, true, false, __FILE, __LINE__, __ENCODING__

类方法的定义:

如下的这些形式意义相同。

class << HelloWorld  # 单例类,可以创建特定对象的方法
  def hello(name)
    puts "this is singleton method defintion, #{name} say hello"
  end
end

def HelloWorld.another_hello(name)
  puts "类型.方法名的定义, #{name} say hello"
end
  
def self.inside # 类方法的定义
  puts "self.inside definition"
end

class << self
  def inside_hello
    puts "this is a inside hello"
  end
end

常量通过类名::常量名来访问,访问控制: public - 以实例方法的形式向外公开方法,private - 仅能内部使用,protected - 相同类中作为实例方法调用。

可以通过public :sub这样的形式,指定特定方法的可访问性,可也使用传统的方式。如下是访问性的一个示例:

class Point
  attr_accessor :x, :y  # 存在 attr_reader和attr_writer 访问器
  protected :x= , :y=

  def initialize(x=0.0, y=0.0)
    @x, @y = x , y
  end

  def swap(other)
    tmp_x, tmp_y = @x, @y
    @x ,@y =  other.x, other.y
    other.x, other.y  = tmp_x , tmp_y
    return self
  end
end

p0 = Point.new
p1 = Point.new(1.0, 2.0)

p [ p0.x, p0.y ]
p [ p1.x, p1.y ]

p0.swap(p1)
p [ p0.x, p0.y ]
p [ p1.x, p1.y ]

p0.x = 10.0 # 报错了NoMethodError

定义类时,默认继承自Object,想要更轻量级类,可以继承自BasicObject,两者的比较如下:

1.9.3-p547 :015 > Object.instance_methods       # 共56个方法
 => [:nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, :singleton_class, :clone, :dup, :initialize_dup, :initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :to_s, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :respond_to_missing?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__] 
1.9.3-p547 :016 > BasicObject.instance_methods  # 共8个方法
 => [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]

备注:Object提供的方法还真是非常丰富,BasicObject提供的instance_eval和instance_exec用来在self环境下执行代码,__send__用来分发代码块。

方法可见性: private - 当前实例内才可调用, protected - 当前实例、同一类的其他实例,及其子类的实例均可调用。如果类中需要创建自己类的实例,使用self.new

模块

模块:表现事物行为部分(具有常量和方法); 类:表现事物的实体及其行为。模块示例定义:

模块的作用:1. 命名空间,2. Mix-in, 不同类共享功能

Rails中,利用模块,将辅助模块混入到模板视图中。

module HelloModule
  Version = "1.0"
  def hello(name)
    puts "Hello, #{name}"
  end

  module_function :hello
end

p HelloModule::Version
HelloModule.hello("Alice")  # 类,模块都是一种对象,这其实就是调用方法的格式
include HelloModule
p Version
hello("Alice")

关于Mix-in,最重要的就是要插入方法的查找规则,诀窍是: 按序上插

extend方法可批量定义对象(可以是类对象或者实例对象)的单例方法:

module Edition    # 将单例方法放在模块中
  def edition(n)
    "#{self}#{n}式 "
  end
end

str = "降龙十八掌"
str.extend(Edition)

p str.edition(4)

所有类都是本身都是Class类的对象,类方法是类对象的实例方法,具体可分为两类: Class类的实例方法,类对象的单例方法。

class MyClass
  extend ClassMethods     # 定义类方法
  include InstanceMethods # 定义实例方法
end

远程数据的处理,web和邮件等不同的应用程序,Net::HTTP, Net::POP,轻松编写网络程序。

鸭子类型实现处理通用化。面向对象设计,重要的是将恰当的信息交给恰当的方法处理,经验,设计模式的知识。

inspect和puts的区别,inspect对开发者用处更大,irb中结果的输出就是inspect方法中的内容。

运算符

Ruby中没有++和–运算符。

赋值运算符必须实现reader和writer两种存取方法。在方法内,创建当前类的对象: self.class.new

一元操作符+,-,~,! 对应的实现方法是: +@, -@, ~@, !@。下标方法: [] 以及 []= 。

错误处理和异常

程序错误的原因: 数据错误,系统错误,程序错误。处理的方法: 排除,忽略,恢复,重试,终止。

异常处理的写法:

begin
  可能发生异常的代码
rescue [=> 引用异常对象的变量]  # 注意:即使不指定变量,自动赋值的变量-$!,发生异常的位置-$@
  发生异常的处理
ensure
  无论如何都要进行的处理
end

异常对象可以调用的方法: class,message,backtrace。如下的例子实现了wc命令:

ltotal, wtotal, ctotal = 0,0,0  # 行合计数,单词数,字数合计

ARGV.each do |file|
  begin
    input = File.open(file)
    l,w,c = 0,0,0  # 打开文件中的行合计数,单词数,字数合计
    input.each_line do |line|
      l += 1
      c += line.size
      line.sub!(/^\s+/,"")
      ary = line.split(/\s+/)
      w += ary.size
    end
    input.close
    printf("%8d %8d %8d %s\n", l, w, c , file)  # 如同c中的函数接口
    ltotal += l
    wtotal += w
    ctotal += c
  rescue => ex
    print ex.message, "\n"
  end
end

printf("%8d %8d %8d %s\n", ltotal, wtotal, ctotal, "total")

重试的语义:在rescue语句中进行一定的处理,然后调用retry,此外,rescue可捕获不同类型,并且可多次出现。

异常类的继承体系,主动抛出异常的方法: raise [异常类] [message] [] 等5种组合方法。

如果异常处理范围是整个方法体,可以省略begin以及end。

灵活的使用块是Ruby的重点。块的使用方法: 循环,隐藏常规处理(块内部进行了清理处理),替换部分算法(Array#sort)。其中,替换部分算法值得一提,下面是其中的代码:

ary = %w(
  Ruby is a open source programming language with a focus
  on simlicity and productivity. It has a elegant syntax 
  that is natural to read eand easy to write
)

call_num = 0
sorted = ary.sort do |a,b|
  call_num += 1 # 调用次数
  a.length <=> b.length  # <=>运算符左右值比较,返回 -1, 0, 1
end
p "ary.sort method:"
p " 排序结果 #{sorted}"
p " 数组的元素数量 #{ary.length}"
p " 调用块的次数 #{call_num}"

call_num = 0
sorted = ary.sort_by { |item| call_num += 1; item.length }

p "ary.sort_by method:"
p " 排序结果 #{sorted}"
p " 数组的元素数量 #{ary.length}"
p " 调用块的次数 #{call_num}"

元素排序算法的公共部分由方法提供,但可替换其中比较的准则。

定义带块的方法

传递块参数,获取块的值

def total(from, to)
  result = 0
  from.upto(to) do |num|
    if block_given? # 这是Object的私有方法,Object.private_methods.include? :block_given?
      result += yield(num)
    else
      result += num
    end
  end
  return result
end

p total(1, 10)  # 1到10的和
p total(1, 20) { |num| num ** 2 }  # 从1到20的2次方的和

yield的语义,将其后的参数传递给代码块。yield的参数的个数和块变量的个数可以不一样,少则填充nil,多则截断,其参数测试的代码如下:

def block_args_test
  yield()
  yield(1)
  yield(1, 2, 3)
end

puts "通过|a|接受块变量"
block_args_test do |a|
  p [a]
end

puts "通过|a,b,c|接受块变量"
block_args_test do |a, b, c|
  p [a, b, c]
end

puts "通过 |*a| 接受块的变量" 
block_args_test do |*a|      # |*a|可以将所有的变量作为数组来接受
  p [a]
end

可以使用break、next等来控制块的执行,break则会返回块调用的地方,next中断当前处理,进入下一步处理。

将块当作对象,可以在接受块的方法之外执行块,或者将块交给其他方法执行。块对象,使用Proc,例子如下:

hello = Proc.new do |name|
  puts "Hello, #{name}"
end

hello.call("world")  # 具有call方法的对象,都是合法的Rack程序
hello.call("Ruby")

将块从一个方法传递到另一个,使用Proc对象,&参数自动包装成Proc对象。块外部的局部变量在块内部访问,而内部变量对外部不可见。

Ruby类

Ruby中一切都是对象,了解语言能操纵的数据类型很重要。数值类的计数: times, upto, downto

数组: %w, %i - 创建符号的方法, to_a , split, []对应的普通方法: at, slice, 插入元素: arr[2,0] - 在下标为2的地方插入数组,

values_at(n1, n2,...)利用索引取出分散的元素,数组可看作带索引的对象,也可看作集合(操作: 交集&,并集 ,差集-),也可将其看作列(队列和堆栈)。|+的区别在连接重复元素的数组

同时访问多个数组: zip方法。

字符串: 包含”或’的字符串时,使用%Q(““)或%q(‘‘)更方便。看到字符串处理部分。

可以通过require来引入特定的gem包或库文件,require可以从LOAD_PATH中寻找特定的库,也可以直接使用完整的文件路径。

require File.dirname(__FILE__) + '/../test_helper'使用相对路径将库载入程序, File.dirname(__FILE__)获取文件的路径。

格式化时间的方法: Time.now.strftime("%Y年%m月%d日")

require

$LOAD_PATH, $LOADED_FEATURES, $: 三个变量

require的作用:

  • 从已存在的Ruby加载路径中加载Gem包
  • 激活Gem包( xxx.activate )

后记

无。




傲娇的使用Disqus