Ruby Basic Summary
Ruby
Ruby是一门通用面向对象脚本语言,此外ruby还对函数式编程提供很好的支持,元编程也是这门语言的强大之处,编写 DSL(Domain Specific Language) 也是具有语法的优越性相关工具
ruby
ruby是Ruby官方的本地解释器,采用c语言写成,与此同时也有JRuby等一些用Java,C++等甚至是Ruby Language写的,Unbuntu等类Unix环境可以通过sudo apt-get install ruby
下载,也可以在Ruby官网进入下载页查看,那里有更好的说明(也可以通过下面的rvm下载)
rvm
rvm(Ruby Version Manager) 是ruby的包管理工具,它使得多版本Ruby解释器能够在本地环境共存,且无缝切换,可以通过在rvm官网的指导下进行下载,安装完成后,执行rvm list known
可以选择自己查看想要的可供下载版本,比如下载jruby-9.2.1.0可以执行
rvm install jruby-9.2.1.0
下载完成后,我们可以通过
rvm list
查看已安装的版本,我们可以执行
rvm use jruby-9.2.1.0
将当前ruby版本切换为jruby-9.2.1.0(当然可以是你已有的其他版本)
RubyGems
RubyGems 是ruby的包管理工具,可以进入RubyGems官网下载安装RubyGems,也可以在其官网查询想要使用的包信息,进行下载,当然我们可以通过本地下载好的RubyGems进行查询及下载(Ruby的库也称呼为gem),比如下载 Nokogiri (Ruby解析XML,HTML的实用库),先用 gem 命令进行查询(支持正则表达式)gem search "^nokogiri$"
找到适合的版本后运行(可以携带指定的版本号)
gem install nokogiri
Bundler
Bundler 可以认为是 RubyGems 功能的增强,Bundler可以使项目工程化,通过Gemfile来指定项目中的gem版本要求(类似于nodejs中的npm ),通过gem install bundler
可以下载bundler,进入我们需要准备的项目目录执行
bundler init
将会在项目目录里产生Gemfile,我们可以编写Gemfile进行项目管理,具体细节可以访问Bundler官网 查看更多细节
Ruby Language
接下来将会尽可能全面的介绍Ruby Language语法及特性常用函数
这里介绍一下ruby里不出意外可以随处调用的函数print arg1, arg2, ...
向标准输出原封不动打印参数(参数可变长)# Output: Marco Epsilon, hello, world
print "Marco Epsilon ", "hello, world"
puts arg1, arg2, ...
向标准输出打印参数(参数可变长),打印每个参数都会接着打印换行符=begin
Output:
Marco Epsilon
hello, world
=end
puts "Marco Epsilon", "hello, world"
p arg1, arg2, ...
直接向标准输出打印arg.inspect的结果和增加的换行符(一般用于调试) # Output: "hello, world"
p "hello, world"
obj.class
从Object继承来的方法,可以查看obj所属类# Output: String
p "hello, world".class
类型(也许说是常用类会更好)
Ruby类型大致分为一下几种数值类型
FixNum
基本整数对应的类型,当超出固定容纳字节长度时,将会自动转化为BignumBignum
Float
浮点数对应类型字符串(String)
数组类型(Array)
范围类型(Range)
哈希表类型(Hash)
正则表达式(Regexp)
调用过程(Proc)
时间(Time)
日期(Date)
符号(Symbol)
...
=begin
Output:
Integer
Float
String
Array
Hash
=end
p 2019.class
p 2.3.class
p "hello, world".class
p [1,2,3].class
p Hash.new({"marco" => "epsilon","rgb" => "lala"}).class
变量(Variables)
Ruby变量分为四种 全局变量,实例变量, 类变量, 局部变量全局变量
全局变量以$variableName声明,一般$variableName采用大写,全局变量在所有脚本中共用一个符号,所以如果不小心多个脚本都声明初始化同一个$variableName,则先加载的脚本就可能会被覆盖,所以遵循几乎所有编程语言的循循教诲,全局变量还是少用为好(ps:Ruby也有一些自带的全局变量,用到再说)实例变量
属于类的实例的变量,使用@variableName声明,可以认为是同一个类各实例不共享的变量,注意必须在类的方法中声明或定义(其实和作用域及当前self有关),可以通过 attr_accessor,attr_reader,attr_writer 控制访问权类变量
属于类的变量,使用@@variableName声明,所有类实例岂可共享,可以需要自行编写函数控制其访问,可以在类中直接声明或定义局部变量
随处都可以声明和初始化的变量,未初始化为nil,且只能在作用域内使用,脱离作用域后自动销毁Example
# 全局变量 不同脚本可共享
$HELLO = "全局变量: hello"
# 局部变量 只存在于当前顶级作用域
hello = "局部变量: hello"
class R
# 设置类的实例变量@hello的访问权
attr_accessor :hello
# 声明初始化类变量@@hello
@@hello = "类变量: hello"
def shared_hello
return @@hello
end
def shared_hello=(value)
@@hello = value
end
end
p $HELLO
p hello
r = R.new
r.hello = "类的实例变量: hello"
p r.hello
p r.shared_hello
r.shared_hello = "类的实例变量: bye,bye!"
p r.shared_hello
=begin
Output:
"全局变量: hello"
"局部变量: hello"
"类实例变量: hello"
"类变量: hello"
"类实例变量: bye,bye!"
=end
结构化编程
Ruby提供一些关键字使得我们能够很好的结构化编程
if condition1 then
...
[elsif] condition2 [then]
...
[else]
...
end
unless condition1 then
...
[else]
...
end
case expression
[when expression, expression, ...] [then]
...
[else]
...
end
while condition do
...
end
until condition do
...
end
for variable1 [ ,variable2, ...] in expression do end
Example
i = 2
# if 语句
if i > 10 then
puts "i大于10"
elsif i == 10 then
puts "i等于10"
else
puts "i小于10"
end
# unless 语句
unless i > 10 then
puts "i小于等于10"
else
puts "i大于10"
end
# case 语句
case i
when 0,1,2,3,4,5 then
puts "#{i}"
else
puts "#{-i}"
end
# while
while i >= 0 do
puts i
i -= 1
end
# until
until i >= 2 do
puts i
i += 1
end
# for
for i in (1..4)
puts i
end
... if codition
... unless condition
... while condition
... until condition
Example
i = 2
puts "i大于1" if i > 1
puts "i>0" unless i < 0
puts (i += 1) while i <= 4
puts (i += 2) until i >= 9
=begin
Output:
i大于1
i>0
3
4
5
7
9
=end
此外还有循环的控制语句,方便跳出循环
break
跳出循环或块调用next
跳出此次循环,(如果在块中调用,则跳出此次块调用,若块要求返回值,可以使用next expression)redo
不检查循环条件,重新进入循环Example
for i in (0 ... 4) do
if i >= 2 then
break
end
puts i
end
for i in (0 ... 4) do
if i == 2
next
end
puts i
end
for i in (0 ... 4) do
if i == 2 then
i += 1
redo
end
puts i
end
=begin
Output:
0
1
0
1
3
0
1
3
3
=end
面向对象
说到面向对象,我们先讨论下何谓面向对象(俨然是成为现代编程语言面向对象规格的面向对象) 一般编程语言的面向对象主要有以下特点:封装性
继承性
多态性
类
Ruby中可以通过 class className... end 来创建类,类是数据的封装,所以类是面向对象的基础,Ruby中类默认继承Object(先在这里提出一点:类在Ruby里其实只是一个全局对象),自定义的类可以通过调用从Object继承而来的new函数来构造对象, new 函数默认会调用 initialize 函数,我们也可以自行编写 initialize 函数来构造对象,此外,Ruby类中没有属性(这样对obj.name不加括号调用的是函数没有好意外的了)这个概念(简单起见下面所说的类的都是Ruby中的类),实例变量只能在实例的作用域定义,而且我们只能通过类实例方法访问,这里的实例变量看起来的确是其他编程语言的属性,不过它们还是有差异的,我们定义完实例变量后其实不能直接从外部访问的,我们只能通过自行编写getter,setter函数来获得修改实例变量,(修改是通过拟态方法 variableName=来完成的)Example
class Dog
def initialize(name)
@name = name
end
def get_name()
@name
end
def set_name(name)
@name = name
end
end
dog = Dog.new("Joke")
p dog.get_name
dog.set_name "Bike"
p dog.get_name
#Output:
=begin
"Joke"
"Bike"
=end
为了使方法更美观和模拟=,我们也可以这样定义:
Example
class Dog
def initialize(name)
@name = name
end
def name()
@name
end
def name=(name)
@name = name
end
end
dog = Dog.new("Joke")
p dog.name
dog.name = "Bike"
p dog.name
# Output:
=begin
"Joke"
"Bike"
=end
这样对于需要暴露诸多实例变量的类都要定义setter,getter方法是无意义的活动,好在超类里帮我们定义了下面几种类宏(本质也是方法)供我们调用控制实例变量的可访问性:
attr_accessor Symbol/String
提供实例变量的符号名或字符串名,生成setter,getter函数,使得实例变量可读写attr_reader Symbol/String
提供实例变量的符号或字符串名,生成getter函数,使得实例变量可读attr_writer Symbol/String
提供实例变量的符号或字符串名,生成setter函数,使得实例变量可写Example
class Dog
attr_accessor :name
attr_reader :sex
attr_writer "age"
def initialize(name, age, sex)
@name = name
@age = age
@sex = sex
end
end
dog = Dog.new("Joke", 3, "女")
dog.name = "Bike"
dog.age = 12
p "dog name: #{dog.name}, sex: #{dog.sex}"
# Output:
=begin
"dog name: Bike, sex: 女"
=end
类的可访问性
由于Ruby类没有属性的概念,实例变量也由方法控制,故Ruby中可访问性主要针对的是类中方法的可访问性,可以设置类的三种可访问性,分别由三种关键字确定:public
public方法设置类中的方法为公有,以实例方法的形式向外公开方法protected
protected方法设置类中的方法为保护成员,该实例方法只能被子类和父类调用private
private方法设置类中的方法为私有成员,该实例方法只能在缺省实例对象时才能被调用(不能指定接收者,但能被子类继承访问(这也是很其他语言有所差别的一点))Example
#!/usr/bin/ruby
# main.rb
class Student
def initialize(name, age)
@name = name
@age = age
end
def learn()
puts "public: #{@name} learn"
end
def run()
puts "protected: #{@name} run"
end
def speak()
puts "private: #{@name} speak"
end
public :learn
protected :run
private :speak
public :initialize
end
# class Child < Parent表示Child继承Parent
class Marco < Student
def initialize(age)
# 调用父类构造函数
super("Marco",age)
end
# 未指定访问性默认public
def play()
# 超类Student继承而来的保护方法run
self.run()
# run() 正确
# 超类Student继承而来的私有方法speak
speak()
# self.speak() 错误
end
end
student = Student.new("Marco", 18)
student.learn()
puts student.private_methods.grep(/^initialize$/)
marco = Marco.new(21)
marco.play
puts marco.private_methods.grep(/^initialize$/)
# Output:
=begin
public: Marco learn
protected: Marco run
private: Marco speak
initialize
=end
类的继承性
Ruby类提供了继承,但是只有单继承,继承可以获得超类所有的方法(包括所有种类可访问性的方法),因此也就没有了C++的公有继承,保护继承,私有继承的概念,Ruby中所有声明的类默认继承于Object,而Object继承于BasicObject,BasicObject是一个空白类(仅含几个必须的方法)
class Parent
attr_accessor :name
def initialize(name)
@name = name
end
end
class Child < Parent
def initialize(name)
super(name)
end
end
child = Child.new("marco")
p child.name
child.name = "epsilon"
p child.name
# Output:
=begin
"marco"
"epsilon"
=end
Example
通过上例我们看到,尽管删除了实例变量,但是只要定义的方法在,我们还是可以让它"死而复生"
class Parent
attr_accessor :name
def initialize(name)
@name = name
end
end
class Child < Parent
def initialize(name)
super(name)
end
end
child = Child.new("marco")
p child.name
child.remove_instance_variable(:@name)
p child.name
child.name = "epsilon"
p child.name
Parent.remove_method(:name=)
p child.name
child.remove_instance_variable(:@name)
p child.name
# Output:
=begin
"marco"
nil
"epsilon"
"epsilon"
nil
=end
多继承? Mix-in by Module
Ruby没有多继承,但是引入了Mix-in的概念(php7的trait也是),通过Mix-in的方法,可以很轻松的"继承"其他已有的方法,ruby的mix-in是通过moudle实现的,通过 module moduleName ... end 就能创建一个module作用域, Module 是Class的超类,所以Class有Module的所有方法,但是Module不能创建对象,需要使用Moudle类的方法可以通过 include moduleName 得到Example
module Magic
def hello()
puts "#{@name}"
end
end
class Try
include Magic
def initialize(name)
@name = name
end
end
try = Try.new("marco")
try.hello()
# Output: marco
错误处理
Ruby的错误处理一般采用的是抛出异常,Ruby使用
begin ... rescue ... end
来包围可能抛出异常的范围和处理范围,通常是
$!
!@
begin
...
可能出现异常
rescue => 引用异常的对象
异常处理
end
异常发生时被自动赋值的全局变量:
异常对象方法:
建议自己编写异常时继承标准库的StandardError或者RuntimeError,这样用户能够更好的适应错误处理的变化,我们可以通过raise关键字抛出异常,用ensure定义无论异常是否后都必须执行的指令
方法名
作用
class
返回异常的类名
message
返回异常的信息
backtrace
返回异常的位置信息($!.backtrace与$@等价)
Example
此外,Ruby还提供了
rescue
修饰符,方便我们简化代码,语法:
expression_1 rescue expression_2
,如果
expression_1
抛出异常,就返回
expression_2
class Error < StandardError
end
class LinkError < Error
end
class ConnectError < Error
end
def try()
begin
#raise LinkError.new("link error may you should check")
raise ConnectError.new("connect error,retry?")
#raise Error.new("unchecked error")
rescue LinkError => link
puts link.message
puts $@
end
end
begin
try()
rescue ConnectError => connect
puts $!.message
rescue Error => error
puts error.message
ensure
puts "destroy resouce..."
end
Example
n = Integer.new("abc") rescue 0
puts n
m = 2 / 0 rescue 0
puts m
# Output:
=begin
0
0
=end
lambda vs block vs Proc
block算是Ruby最常用的基础设施了,通过它很方便剥离可变的逻辑,它主要有以下几种用途:
循环逻辑
控制逻辑
...
lambda对参数要求更严格,形参和实参个数必须相同,而non-lambda和block则对于实参少于形参时用nil代替,多于时忽略
non-lambda和block尽量不要使用return关键字,因为non-lambda和block会在调用它的作用域退出到创建它的作用域上一层,而lambda则直接退出调用它的作用域
numbers = [1, 2, 3, 4, 5]
numbers.each do |num|
puts num
end
numbers = [1, 2, 3, 4, 5, 6]
numbers.select! do |num|
num % 2 == 0
end
numbers.each do |num|
puts num
end
lambda和Proc区别和lambda和block区别类似
numbers = [7,4,2,3,6,5,1]
numbers.sort! &lambda {|i, j|
i <=> j
}
numbers.each &-> (num) {puts num}
对于Proc分为non-lambda对象(主要通过Proc.new创建),lambda对象(通过lambda关键字创建),此外一个block代码块(block不是对象),non-lambda和block行为差不多相同,lambda则与两者有所不同(转化为block性质不变),主要体现在以下两点:
# lambda VS non-lambda Proc(regular Proc) VS block 参数异同点
numbers = [1, 2, 3, 4, 5, 6]
numbers.each do |i, j|
if j == nil then
puts i
end
end
begin
numbers.each &-> (i, j) {puts i if j == nil}
rescue ArgumentError => e
puts "Step 2 use lambad transform occur ArgumentError #{e.message}"
end
procdure = Proc.new { |i,j| puts i if j == nil }
numbers.each &procdure
# lambda VS non-lambda Proc(regular Proc) VS block return语句异同点
def return_test(src, &block)
numbers = [5,4,2,7,1,6,3]
if block_given? then
puts "#{src} begin call"
numbers.sort! &block
puts "#{src} result: #{numbers}"
end
end
def test_block()
return_test "block" do |i, j|
return i <=> j
end
end
def test_proc()
procdure = Proc.new do |i, j|
return i <=> j
end
return_test "non-lambda Proc (regular Proc)", &procdure
end
def test_lambda()
return_test "lambda", &->(i, j) {return i <=> j}
end
test_block()
test_proc()
test_lambda()