Ruby

基础

命令行

ruby <ruby源文件>			  # 运行
ruby -e "puts RUBY_VERSION"	# 直接执行一段代码
ruby -c <ruby源文件>		  # 检查语法,不执行代码

Linux下ruby源文件第一行指明解释器的路径:

#!/usr/bin/ruby

基本语法

  • Ruby把分号和换行符解释为语句的结尾(但是,如果在行尾遇到运算符,比如+-\等,它们表示一个语句的延续);

  • Ruby的标识符是大小写敏感的,标识符的名称可以包含字母、数字和下划线。

  • 注释:可以单行和多行注释。

    # 我是注释,请忽略我。
    
    =begin
    这是注释。
    这也是注释。
    =end

  • 代码块:可以使用{}字符或doend关键字来分隔代码块。在if语句中,代码块由thenend关键字定界,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

  • truefalsetrueTrueClass 的实例,falseFalseClass 的实例。

    puts true.class  	# TrueClass
    puts false.class 	# FalseClass

    Ruby 中,只有 falsenil 被视为“假”,其他所有值(包括 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)

truefalse

符号(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		# 哈希转换为数组

运算符

算术运算符

+-*/%**没有自增自减运算符

逻辑运算符

&&||!。还有andornot,意思相同,只是优先级低于符号的。

关系运算符

  • <<=>>=!=<=>(字符串中提到了)。

  • ==:比较两个对象的值是否相等;

  • 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

breakcontinue

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"

Ruby
https://shuusui.site/blog/2024/12/05/ruby/
作者
Shuusui
发布于
2024年12月5日
更新于
2024年12月11日
许可协议