Ruby学习笔记
前言
虽然从事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