Ruby
基础
命令行
ruby <ruby源文件> # 运行
ruby -e "puts RUBY_VERSION" # 直接执行一段代码
ruby -c <ruby源文件> # 检查语法,不执行代码
Linux下ruby源文件第一行指明解释器的路径:
#!/usr/bin/ruby
基本语法
Ruby把分号和换行符解释为语句的结尾(但是,如果在行尾遇到运算符,比如
+
、-
或\
等,它们表示一个语句的延续);Ruby的标识符是大小写敏感的,标识符的名称可以包含字母、数字和下划线。
注释:可以单行和多行注释。
# 我是注释,请忽略我。 =begin 这是注释。 这也是注释。 =end
代码块:可以使用
{}
字符或do
和end
关键字来分隔代码块。在if
语句中,代码块由then
和end
关键字定界,then
可以省略。
输入输出
这些方法其实都是
Kernel
类的方法,只是一般省略了。
puts
输出会换行;print
不会换行;p
输出对象的原始形式(如字符串带引号,数组带括号);gets
用于读取一行字符串。
print "What is your name? "
name = gets
puts "Hello #{name}"
变量
常量以大写字母开头。原则上不应该修改常量的值,如果修改了会报warning,但能修改成功。
变量以小写字母或下划线开头。变量前的特殊字符表示作用域。
局部变量
变量前没有特殊字符表示。
def method1
x = 10
puts local_variables # 输出:x
end
method1
p x # 报错
全局变量
$
开头,在任何地方都有效。
$gb = 5
def method1
puts $gb
puts global_variables.include? :$gb # 加:表示符号本身,输出true
end
method1
实例变量、类变量
实例变量@
开头,仅在一个对象内部有效;类变量@@
开头,该类的所有实例共享该变量。
class Being
@@count = 0
def initialize name
@name = name
@@count = @@count + 1
end
def output
puts "#{@name} #{@@count}"
# puts instance_variables 可以打印实例变量,输出:@name
end
end
b1 = Being.new "Being 1"
b2 = Being.new "Being 2"
b3 = Being.new "Being 3"
b1.output # Being 1, 3
b2.output # Being 2, 3
b3.output # Being 3, 3
伪变量
看起来像变量,但不能赋值或修改。
self
:表示当前对象。puts self # main class MyClass puts self # MyClass end
nil
:类似其他语言的null
,是NilClass
的唯一实例。puts nil.class # NilClass
true
和false
:true
是TrueClass
的实例,false
是FalseClass
的实例。puts true.class # TrueClass puts false.class # FalseClass
Ruby 中,只有
false
和nil
被视为“假”,其他所有值(包括0
和空字符串""
)都为“真”。
预定义变量
$0
存储当前脚本名称;$*
变量存储命令行参数;$$
存储脚本的PID。
$ ruby test.rb aaa bbb
test.rb
aaa
bbb
14884
BEGIN和END语句
BEGIN
中的代码最先执行,END
中的代码最后执行。
puts "这是主 Ruby 程序"
BEGIN {
puts "初始化 Ruby 程序"
}
END {
puts "停止 Ruby 程序"
}
数据类型
布尔值(TrueClass、FalseClass)
即true
和false
。
符号(Symbol)
不可变的轻量字符串(有时我们不需要字符串对象的全部功能,通常用于枚举对象、哈希的键等)。
所有符号都存储在符号表中(可以打印Symbol.all_symbols
查看)。
puts :name # name
puts :name.class # Symbol
# Symbol的方法数比String少得多
puts :name.methods.size # 81
puts "Jane".methods.size # 181
# 相同的Symbol具有相同的ID,字符串则不然
puts :name.object_id # 72028
puts :name.object_id # 72028
puts "Jane".object_id # 60
puts "Jane".object_id # 80
数值类型(Numeric)
整数(Integer)
- 支持任意长度的整数(类似python);
- 可以使用前导符号:
0
开头表示8进制,0x
开头表示16进制,0b
开头表示2进制; - 数字中可以使用
_
以增强可读性(类似verilog)。
整数除法的结果也是整数。
浮点数(Float)
基于C语言的double类型实现的,精度有限。
123.4
1e6
1e-5
# 两种方法打印格式化的小数
puts sprintf "%.4f" % (1/3.0)
printf "%.7f" % (5/3.0)
如果要任意精度可以使用BigDecimal
标准库。
require 'bigdecimal'
num = BigDecimal("1.123456789012345678901234567890")
有理数(Rational)
puts Rational(3, 4) # 3/4
puts 2/3r # 2/3
puts 2.5.to_r # 5/2
复数(Complex)
puts Complex(1, 2) # 1+2i
puts 1+2i # 1+2i
范围(Range)
表示连续值的范围。..
包含结束值;...
不包含结束值。
range1 = 1..5 # 包含1到5
range2 = 1...5 # 包含1到4
puts range1.to_a
字符串(String)
创建
website = "google.com" # 字面值
website = String.new "zetcode.com" # 对象
引号与转义
- 双引号支持插值
#{}
,单引号不支持; - 双引号支持更多的转义字符(
\n
等),单引号仅支持\\
和\'
。
puts "Hello, #{name}" # Hello, Ruby
puts 'Hello, #{name}' # Hello, #{name}
puts "Hello\nWorld!" # 有换行
puts 'Hello\nWorld!' # Hello\nWorld!
访问元素
msg = "Ruby language"
# 测试字符串是否为msg的子串
puts msg["Ruby"] # Ruby
puts msg["Python"] # 无输出
# 索引
puts msg[0] # R
puts msg[-1] # e
# 切片(前闭后开)
puts msg[0, 3] # Rub
puts msg[0, msg.length] # Ruby language
# 范围
puts msg[0..9] # Ruby langu
多行字符串
三种方法。第三种(Here Document语法)EOF可以是任意字符。
puts "I hear Mariachi static on my radio
And the tubes they glow in the dark"
puts %/Carmelita hold me tighter
I think I'm sinking down/
puts <<EOF
Well, I'm sittin' here playing solitaire
With my pearl-handled deck
EOF
变量插值
name = "Jane"
age = 17
puts "#{name} is #{age * 2} years old"
puts "%s is %d years old" % [name, age]
连接
四种方式都可以。
puts "Ruby" + " programming" + " language"
puts "Ruby" " programming" " language"
puts "Ruby" << " programming" << " language"
puts "Ruby".concat(" programming").concat(" language")
冻结
在 Ruby
中,默认情况下字符串是可变的,调用freeze
使之不可变。
msg = "Jane is "
msg << "17 years old"
puts msg
msg.freeze
msg << "and she is pretty" # 报错
比较
puts "aa" == "ab"
puts "Jane".eql? "Jane"
# 左边小为-1,左边大为1,相等为0
puts "ab" <=> "ba" # -1
puts "ab".casecmp "ba" # -1
常用方法
word = "Determination"
# 字符串长度
puts word.size # 13
# 是否为空
puts word.empty? # false
# 测试是否含有子串
puts word.include? "tion" # true
# 是否以指定字符串开头结尾
puts word.start_with? "De" # true
puts word.end_with? "tion" # true
# 大小写转换
puts word.upcase # DETERMINATION
puts word.downcase # determination
# 首字母大写
puts "word".capitalize # Word
puts "aaBB".swapcase # AAbb
# 清空
word.clear
# 显示原始字符、去除末尾\n
msg = "Jane\t17\nThomas\t23\n"
puts msg.inspect # "Jane\t17\nThomas\t23\n"
puts msg.chomp.inspect # "Jane\t17\nThomas\t23"
格式化
基本同python,更详细的自己查。
puts "%10d" % 16567 # 16567
puts "%s" % "zetcode" # zetcode
puts "%.2f" % 123.12345 # 123.12
数组(Array)
可以存储任意类型的对象。类似python列表。
arr = [1, "Ruby", :symbol]
arr.each do |it|
puts it
end
哈希(Hash)
键值对的集合。类似python字典。
domains = {
:de => "Germany",
:sk => "Slovakia",
:us => "United States",
:no => "Norway"
}
puts domains.keys
puts domains.values
类型转换
p "12".to_i # 转换为整数
p "13".to_f # 转换为浮点数
p "12".to_r # 转换为有理数
p "13".to_c # 转换为复数
p 124.to_s # 转换为字符串
p "Jane".to_sym # 转换为符号
p domains.to_a # 哈希转换为数组
运算符
算术运算符
+
、-
、*
、/
、%
、**
。没有自增自减运算符。
逻辑运算符
&&
、||
、!
。还有and
、or
、not
,意思相同,只是优先级低于符号的。
关系运算符
<
、<=
、>
、>=
、!=
、<=>
(字符串中提到了)。==
:比较两个对象的值是否相等;eql?
:判断两个对象的值是否相等,且类型相同;===
:判断一个对象是否符合某个模式或属于某个范围。
puts 5 == 5.0 # true
puts 5.eql? 5.0 # false
puts (1..10) === 5 # true(5在范围内)
puts String === "abc" # true(abc是String的实例)
位运算符
&
、|
、~
、^
、<<
、>>
。
复合赋值运算符
+=
、-=
、*=
、**=
、/=
、%=
、&=
、|=
……
三元运算符
cond ? exp1 : exp2
。
成员访问运算符
::
用于访问模块或类中的常量,也可以访问方法(很少用);.
用于访问模块或类中的方法。
class MyMath
Pi = 3.1415926535
def fun
puts Pi
end
end
module People
Name = "People"
def self.fun # 有self是模块方法
puts Name
end
end
puts MyMath::Pi # 3.1415926535
m = MyMath.new
m.fun # 3.1415926535
m::fun # 3.1415926535
puts People::Name # People
People::fun # People
People.fun # People
流程控制
if
中括号表示可以省略。
if num < 0 [then]
puts "#{num} is negative"
elsif num == 0
puts "#{num} is zero"
elsif num > 0
puts "#{num} is positive"
end
case
domain = gets.chomp
case domain
when "us"
puts "United States"
when "de"
puts "Germany"
when "sk"
puts "Slovakia"
else
puts "Unknown"
end
while
在条件为true时执行。
while i < 10 do
i = i + 1
sum = sum + i
end
在条件为false时执行。
until i >= 10 do
i = i + 1
sum = sum + i
end
for
# 遍历范围
for i in 1..5
puts "当前值是:#{i}"
end
puts i # 5
# 遍历数组
arr = ["Ruby", "Python", "Java"]
for lang in arr
puts "编程语言:#{lang}"
end
# 遍历哈希
hash = {name: "Ruby", version: 3.1, type: "语言"}
for key, value in hash
puts "#{key} => #{value}"
end
each
each语句的迭代变量(如下面的num)在外部作用域不可见,for是可见的。
arr = [1, 2, 3]
arr.each do |num|
puts num
end
arr.each { |num|
puts num
}
puts num # 报错
loop
无限循环。
loop do
puts "这是一个无限循环"
end
break、next
同break
和continue
。
redo
重新执行当前迭代。注意避免死循环。
flag = 0
for i in 1..3
puts i
if i == 2 and flag ==0
flag = 1
next
end
end
# 1 2 2 3
数组
创建
# 用字面值创建
arr1 = [1, 2, 3] # [1, 2, 3]
arr1 = [1, 2, [3, 4, [5, 6]]] # 多维
# 创建空数组
arr2 = Array.new # []
arr2.push 1 # [1]
# 创建指定长度的数组,默认填充nil,可以不写()
arr3 = Array.new 3 # [nil, nil, nil]
arr4 = Array.new(3, "a") # ["a", "a", "a"]
# 使用代码块动态填充数组
arr5 = Array.new(3) {|i| i*i} # [0, 1, 4]
# 使用%w创建,空格分隔,无需引号
arr6 = %w{apple banana cherry} # ["apple", "banana", "cherry"]
# 用to_a转换为数组
arr7 = (1..3).to_a # [1, 2, 3]
# 用Array转换为数组
arr8 = Array(1..3) # [1, 2, 3]
读取
arr = %w{a b c d e f g h}
puts arr.first # a
puts arr.last # h
puts arr.at(3) # d
puts arr[0] # a
puts arr[-1] # h
puts arr[0, 3].inspect # ["a", "b", "c"]
puts arr[2..6].inspect # ["c", "d", "e", "f", "g"]
puts arr[2...6].inspect # ["c", "d", "e", "f"]
puts arr.values_at(1, 3, 5).inspect # ["b", "d", "f"]
puts arr.values_at(-1, -3).inspect # ["h", "f"]
遍历
puts arr.inspect # 以字符串形式输出,一行
puts arr # 每行一个元素,和下面的each效果相同
arr.each do |e|
puts e
end
arr.each_with_index do |num, idx| # 附带下标idx
puts "value #{num} has index #{idx}"
end
添加
arr = []
arr.insert(0, 'E', 'F', 'G') # 从指定位置插入
arr.push 'H' # 追加
arr.push('I', 'J', 'K') # 追加多个
arr << 'L' << 'M' # 同push
arr.unshift('A', 'B', 'C') # 加在前面
arr.insert(3, 'D') # 从指定位置插入
puts arr.inspect
# ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"]
删除
arr = %w{ a b c d e}
# 删除最后一个元素
arr.pop # ["a", "b", "c", "d"]
# 删除第一个元素
arr.shift # ["b", "c", "d"]
# 删除特定位置的元素
arr.delete_at(0) # ["c", "d"]
# 删除特定元素
arr.delete('d') # ["c"]
# 删除所有元素
arr.clear # []
常用方法
arr1 = %w{a b c}
arr2 = %w{d e f}
# 连接数组
arr = arr1 + arr2
arr = arr1.concat arr2
# 长度
puts arr.length
# 比较数组,复制数组,删除数组某个元素
puts arr.eql? arr.dup # true
puts arr.eql? arr.dup.delete_at(0) # false
# 判断是否为空
puts arr.empty?
查找
numbers = [1, 2, 2, 2, 3, 4, 5, 8, 11]
# 从左往右查找,返回下标
puts numbers.index 2 # 1
# 从右往左查找,返回下标
puts numbers.rindex 2 # 3
# 是否包含某元素
puts numbers.include? 3 # true
哈希
创建
# 字面值
domains = {
"de" => "Germany",
"sk" => "Slovakia",
"hu" => "Hungary",
"us" => "United States",
"no" => "Norway"
}
# 创建空对象
names = Hash.new
读取
stones = {
1 => "garnet",
2 => "topaz",
3 => "opal",
4 => "amethyst"
}
# 读取给定键的值
puts stones.fetch 1 # garnet
puts stones[2] # topaz
# 获得多个值,返回数组
puts stones.values_at(1, 2, 3).inspect
# ["garnet", "topaz", "opal"]
遍历
# 遍历键和值
stones.each {
|k, v| puts "Key: #{k}, Value: #{v}"
}
# 遍历键
stones.each_key {
|key| puts "#{key}"
}
# 遍历值
stones.each_value {
|val| puts "#{val}"
}
添加
# 添加键值对
names[:a] = "Jane" # 方式一
names.store(:b, "Thomas") # 方式二
删除
names[1] = "Jane"
names[2] = "Thomas"
names[3] = "Robert"
names[4] = "Julia"
names[5] = "Rebecca"
# 通过键删除
names.delete 4
# {1=>"Jane", 2=>"Thomas", 3=>"Robert", 5=>"Rebecca"}
# 删除符合条件的,不改变原哈希
puts names.reject { |k, v| v =~ /R.*/ }
# {1=>"Jane", 2=>"Thomas"}
puts names
# {1=>"Jane", 2=>"Thomas", 3=>"Robert", 5=>"Rebecca"}
# 删除符合条件的,同时改变原哈希
puts names.delete_if { |k, v| k<=3 } # {5=>"Rebecca"}
puts names # {5=>"Rebecca"}
常用方法
puts domains.length # 长度
puts domains.size # 同上
puts domains.keys.inspect # 键的数组
puts domains.values.inspect # 值的数组
names2 = names1.dup # 复制
puts names1.eql? names2 # 判断是否等于
puts names1.empty? # 判断是否为空
names1.clear # 删除所有项
puts domains.include? :no # 键是否存在
puts domains.key? :me # 同上
puts domains.value? "Germany" # 值是否存在
names1 = {
1 => "Jane"
}
names2 = {
2 => "Thomas"
}
# 合并,不修改原对象
names = names1.merge names2
puts names # {1=>"Jane", 2=>"Thomas"}
puts names1 # {1=>"Jane"}
# 合并,带!表示修改了原对象
names = names1.merge! names2
puts names # {1=>"Jane", 2=>"Thomas"}
puts names1 # {1=>"Jane", 2=>"Thomas"}
面向对象
对象的创建
class Being
end
b = Being.new
puts b # 打印对象实际上是打印to_s方法的返回值
构造函数
class Person
# 构造函数
def initialize name # 参数加不加括号都行
@name = name
end
def get_name # 实例变量只能通过方法访问
@name
end
end
p1 = Person.new "Jane"
puts p1.get_name
可以设置默认参数,必须按参数顺序赋值,模拟了构造函数重载的行为。
class Person
def initialize(name="unknown", age=0)
@name = name
@age = age
end
def to_s
"Name: #{@name}, Age: #{@age}"
end
end
p1 = Person.new
p2 = Person.new "Robert"
puts p1 # Name: unknown, Age: 0
puts p2 # Name: Robert, Age: 0
方法与访问修饰符
调用方法:
puts p1.get_name # 用点运算符
puts p1.send(:get_name) # send方法,参数是方法名的符号
在Ruby中,所有数据成员都是私有的,访问修饰符只能在方法上使用。
public
:可被任意对象调用,方法默认是public
的。protected
:只能被类及其子类的对象调用。private
:仅限类的内部使用。
class Some
def initialize
# 以下调用都可以
method1
self.method1
method2
self.method2
method3
self.method3
end
public # 表示之后的部分是public
def method1
puts "public method1 called"
end
private # 表示之后的部分是private
def method2
puts "private method2 called"
end
# private :method2 # 也可以直接指定某函数是private
protected # 表示之后的部分是protected
def method3
puts "protected method3 called"
end
end
s = Some.new
puts "-----------"
s.method1
# s.method2 # 报错
# s.method3 # 报错
继承
Ruby中,数据成员和方法的可见性不受继承的影响,比如private方法会继承给子类(在Java中private无法继承)。
class Being
def initialize
puts "Being class created"
end
end
# 使用小于符号表示继承
class Human < Being
def initialize
super # 调用父类的构造
puts "Human class created"
end
end
Human.new
p Human.ancestors # 打印祖先列表
# [Human, Being, Object, Kernel, BasicObject]
super
会调用父类中相同名称的方法。如果该方法没有参数,它将自动传递其所有参数;写super()
才不会将任何参数传递给父方法。
class Base
def show x=0, y=0
p "x: #{x}, y: #{y}"
end
end
class Derived < Base
def show x, y
super # "x: 3, y: 3"
super x # "x: 3, y: 0"
super x, y # "x: 3, y: 3"
super() # "x: 0, y: 0"
end
end
d = Derived.new
d.show 3, 3
属性访问器
attr_reader
:创建get方法;attr_writer
:创建set方法,同时创建实例变量;attr_accessor
:等于上面两个。
class Car
attr_reader :name, :price
attr_writer :name, :price
# attr_accessor :name, :price
end
c1 = Car.new
c1.name = "Porsche"
c1.price = 23500
puts "The #{c1.name} costs #{c1.price}"
类常量
类常量不属于具体的对象,属于这个类。可以在类的内部直接访问类常量,就像是访问变量一样;在类的外部访问类常量需要用::
。
class MMath
PI = 3.141592
end
puts MMath::PI
运算符重载
class Circle
attr_accessor :radius
def initialize r
@radius = r
end
def +(other)
Circle.new @radius + other.radius
end
end
c1 = Circle.new 5
c2 = Circle.new 6
c3 = c1 + c2
puts c3.radius
类方法
Ruby的方法可以分为类方法和实例方法。类方法只能在类上调用,不能在类的实例上调用,且类方法不能访问实例变量。
class Circle
def initialize x
@r = x
end
# 以self关键字开头的方法是类方法
def self.info
"This is a Circle class"
end
def area
@r * @r * 3.141592
end
end
p Circle.info
c = Circle.new 3
p c.area
模块
Ruby
Module
是方法、类和常量的集合,可以用于对相关的类进行分组。方法和常量可以放入单独的模块中。模块不能有实例。有点类似Java的包、C++的namespace,但不完全相同。
puts Math::PI
# include之后就可以直接使用
include Math
puts PI
puts sin 2
# 另一个实例
module Device
def switch_on ; puts "on" end
def switch_off ; puts "off" end
end
module Volume
def volume_up ; puts "volume up" end
def vodule_down ; puts "volume down" end
end
module Pluggable
def plug_in ; puts "plug in" end
def plug_out ; puts "plug out" end
end
class CellPhone
include Device, Volume, Pluggable
def ring
puts "ringing"
end
end
cph = CellPhone.new
cph.switch_on # on
cph.volume_up # volume up
cph.ring # ringing
异常
异常是对象,是内置Exception
类的后代。
捕获异常
begin
a = 5 / 0
rescue => e
p e # #<ZeroDivisionError: divided by 0>
else # 没有异常时执行
puts "right"
ensure # 无论如何都会执行
puts "always excute"
end
raise
age = 17
begin
if age < 18
raise "Person is a minor"
end
puts "Entry allowed"
rescue => e
puts e # Person is a minor
end
catch和throw
catch
包围一个块,如果块中throw
了对应的异常,则跳出该块。
# catch可以有返回值
result = catch(:exit_point) do
value = 42
throw(:exit_point, value) # 将值 42 传递给 catch
"This will never be executed"
end
puts result # 42
可以用于跳出深层循环:
catch(:done) do
(1..10).each do |i|
(1..10).each do |j|
puts "i: #{i}, j: #{j}"
throw(:done) if i * j > 30 # 跳出整个 catch 块
end
end
end
puts "Loop exited"