Posts Ruby入门
Post
Cancel

Ruby入门

一、 前言

在使用Jekyll的过程中,因为了解不够充分,出过不少问题,其中大部分都是因为没有对应的gem导致的。这个问题困扰了我好一段时间,最后详细了解了bundler之后,才解决了依赖的安装问题,在安装gem包时发现jekyll的依赖十分丰富,这种开发模式让我对Ruby产生一些兴趣,以下写出入门部分的一些内容,希望能有所帮助

只打算在一篇之内写一些基础的入门部分,稍稍体会Ruby编程的美妙之处,它的风格确实让我感受到是“程序员最好的朋友”,毕竟Ruby目前最大的用途还是做Web框架的开发,其中最成功的例子就是ROR(Ruby On Rails)框架,这个语言因为ROR在国外火过一段时间;至于国内的情况,在2014年左右的创业潮中,由于Ruby易上手的特性,热度也有高的时候;但是创业热退去之后,现在又变得不温不火了。从Ruby China(中国最大的Ruby论坛)来看,Ruby在国内呈颓势,原因是多方面的,就算它是“程序员最好的朋友”,但如果不是“老板的朋友”,它也不太可能抢到很多份额。不过,抛开功利心,在闲暇时间里用Ruby为自己写一些东西,不失为一种快乐。

二、 Ruby简介

  • Ruby是一种纯粹的面向对象编程语言。它由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)创建于1993年
  • Ruby 是”程序员的最好的朋友”
  • Ruby 的特性与 Smalltalk、Perl 和 Python 类似。Perl、Python 和 Smalltalk 是脚本语言。Smalltalk 是一个真正的面向对象语言。Ruby,与 Smalltalk 一样,是一个完美的面向对象语言。使用 Ruby 的语法比使用 Smalltalk 的语法要容易得多。

Ruby的特性

  • 开源,在Web上免费使用,但需要许可证
  • 通用的、解释的编程语言
  • 虚拟机、一次编写,到处运行
  • 完全面向对象
  • 是一种类似于 Python 和 Perl 的服务器端脚本语言
  • 可以用来编写通用网关接口(CGI)脚本
  • 可以内嵌至HTML
  • 语法简单、容易上手
  • 与 C++ 和 Perl 等许多编程语言有着类似的语法
  • 可扩展性强,用 Ruby 编写的大程序易于维护
  • 可用于开发的 Internet 和 Intranet 应用程序。
  • 多操作系统支持
  • 支持许多 GUI 工具,比如 Tcl/Tk、GTK 和 OpenGL
  • 可以很容易地连接到 DB2、MySQL、Oracle 和 Sybase
  • 有丰富的内置函数,可以直接在 Ruby 脚本中使用

三、 在Ubuntu上安装Ruby

1. 添加repository

1
2
3
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:brightbox/ruby-ng
$ sudo apt-get update

2. 安装packages

每个版本都有对应的package,在安装的时候指定即可,最好要和开发package一起安装

比如安装Ruby2.6和对应的开发包

1
2
3
4
5
# 安装ruby
$ sudo apt-get install ruby2.6 ruby2.6-dev 

# 查看版本
$ ruby -v

可以安装多个版本的ruby,通过ruby-switch切换默认版本

1
2
3
4
5
6
7
8
9
10
11
12
# 安装ruby-switch
$ sudo apt-get install ruby-switch

#列出已安装的Ruby版本
$ ruby-switch --list
ruby2.5
ruby2.6

#切换版本
$ sudo ruby-switch --set ruby2.5
update-alternatives: using /usr/bin/ruby2.5 to provide /usr/bin/ruby (ruby) in manual mode
update-alternatives: using /usr/bin/gem2.5 to provide /usr/bin/gem (gem) in manual mode

gem二进制文件可能不会运行当前设置的默认版本ruby,它更倾向于运行它被安装时的ruby环境

可以指定版本

1
2
$ ruby2.6 -S bundle -v
Bundler version 2.1.4

四、 开始学习

长文警告

1. ruby解释器

Ruby是一种解释型语言,只能在对应的虚拟机中运行;使用虚拟机运行ruby程序有两种方法:通过IRB和命令行

1.1 从命令行运行代码

将代码保存在文件中,是编写程序的耐用方法

示例:hello.rb

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/ruby   
# filename : first-ruby.rb

class Sample
  def hello
    puts "Hello, World!"
  end
end

s = Sample.new
s.hello

可以使用下面的命令运行程序

1
2
$ ruby hello.rb
Hello, World!

1.2 从IRB运行代码

Ruby是第一批推广REPL的编程语言,REPL即”Read”, “Evaluate”, “Print”, “Loop”

而所谓IRB,就是Interactive Ruby,像一个计算器一样,常在开发过程中用来做实验或者调试。

按Ctrl+d退出irb模式

2. Ruby变量

变量是对数据的抽象

2.1. 不必声明

在很多语言中,给变量赋值前必须要进行声明,而在Ruby这里不需要;同Python类似,当为变量分配值的时候就创建了对应的变量,下面是在IRB中一个例子:

1
2
irb(main):001:0> m=5
=> 5

代码下面的一行表示语句返回的值,方便程序调试

2.2 随时更改数据类型

Ruby是弱类型编程语言,其变量可以容纳所有类型的数据,还可以随时修改,例如:

1
2
3
4
irb(main):003:0> m=10
=> 10
irb(main):004:0> m="This is a sentence."
=> "This is a sentence."

2.3 命名约定

  • 始终以小写字母开头(允许下划线,虽然不常见)
  • 不能使用空格
  • 不能使用特殊字符

Ruby开发者常见的风格偏好

  • 使用使用蛇形大小写,即:名称中的每个字都是小写字母,并用下划线_连接
  • 以其内容的含义命名,而不是其内容的类型
  • 不使用缩写

大体的风格就是这样:count, total_count,student_name

一些不符合常见风格的命名:

  • studentName: 使用了驼峰型命名风格
  • int_count: 包含了数据类型的内容
  • cnt: 使用了缩写

3. 字符串

3.1 定义

Ruby的字符串被定义为两个引号和其中的内容,最短的字符串是空串""

3.2 子字符串

顾名思义,子字符串是字符串的一部分,和集合范畴的意义相同。可以通过类似数组索引的方法访问子串,下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
irb(main):005:0> string="This is a string."
=> "This is a string."
irb(main):006:0> string[1]
=> "h"
irb(main):007:0> string[2..4]
=> "is "
irb(main):009:0> string[2...4]
=> "is"
irb(main):010:0> string[-1]
=> "."
irb(main):011:0> [-3..-1]
=> [-3..-1]
irb(main):013:0> string[-3..1]
=> ""

注意到几点:

  • 可以直接通过单个索引,访问单个字符
  • 可以通过首尾字符的索引访问一个子串
  • 使用首尾索引的时候中间可以有2/3个点,且意义不同
    • 两个点包括第二个索引的内容
    • 三个点不包括第二个索引的内容
  • 可以使用负数索引,-1对应该串最后一个字符,类推
  • 不能同时使用负数索引和正数索引,会返回空串

3.3 字符串常用方法

.length:返回字符串长度,包括空格

1
2
irb(main):015:0> "Hello".length
=> 5

.split:分解字符串,返回字符串数组,无参数时默认使用空格作为分隔标志

1
2
3
4
irb(main):018:0> "this is also a sentence~".split
=> ["this", "is", "also", "a", "sentence~"]
irb(main):019:0> "this is also a sentence~".split("i")
=> ["th", "s ", "s also a sentence~"]

.sub和.gsub,查找替换功能,传入两个参数:第一个用于查找,第二个用于替换;.gsub是.sub的全局版本——.sub只替换第一个,.gsub全部替换

1
2
3
=> "thIs is also a sentence~"
irb(main):021:0> "this is also a sentence~".gsub("i", "I")
=> "thIs Is also a sentence~"

3.4 字符串组合

第一种方法是用+号连接

1
2
irb(main):023:0> "before" + " and " + "after"
=> "before and after"

另一种方法是使用插值标记#{}直接插入代码,其返回值会替换这个位置

1
2
3
4
5
6
irb(main):023:0> "before" + " and " + "after"
=> "before and after"
irb(main):024:0> "1+1=#{1+1}"
=> "1+1=2"
irb(main):033:0> "3 > 1 is #{3>1? "true":"false"}"
=> "3 > 1 is true"

3.4 *运算符的字符串运算

将字符串作为*运算符的第一操作数,将整数作为其第二操作数,结果将第一操作数(字符串)重复第二操作数(整数)次

查看示例:

1
2
irb(main):001:0> "string! "*3
=> "string! string! string! "

4. 符号

符号是较以往认知的比较特殊的存在,我认为与其翻译成“符号”,不如翻译成“标志”更合适一些。

实际上,在 Ruby 内部操作符、变量等名字本身就是作为Symbol 处理的,例如当你定义一个实例变量时, Ruby 会自动创建一个 Symbol 对象,例如 @test 对应为 :@test

  • 它像字符串又不是字符串
  • 一个符号之前是冒号,如:user
  • 符号和字符串的一个区别是,如果文本是一个数据,那么它是一个字符串;如果它是一个代码,那么它是一个符号

查看示例:

1
2
3
4
5
6
7
8
irb(main):003:0> "string ".object_id
=> 70368558230520
irb(main):004:0> "string ".object_id
=> 70368558232860
irb(main):005:0> :symbol.object_id
=> 885468
irb(main):006:0> :symbol.object_id
=> 885468

可以看到,符号是惟一的标识符,表示静态的值,而字符串表示更改的值

上面的示例中,为字符串创建了两个object_id,为Symbol创建了仅一个object_id,这体现了Symbol的静态性和唯一性。

要详细了解符号,推荐阅读CSDN-理解 Ruby Symbol (Ruby中的符号)

5. 数值

数值有两种基本类型:整数和浮点数,可以用.methods查看数值的所有方法

1
2
irb(main):002:0> 5.methods
=> [:-@, :**, :<=>, :upto, :<<, :<=, :>=, :==, :chr, :===, :>>, :[], :%, :&, :inspect, :*, :+, :ord, :-, :/, :size, :succ, :<, :>, :to_int, :coerce, :to_s, :to_i, :to_f, :divmod, :to_r, :fdiv, :modulo, :remainder, :abs, :magnitude, :integer?, :floor, :ceil, :round, :truncate, :^, :odd?, :even?, :allbits?, :anybits?, :nobits?, :downto, :times, :pred, :pow, :bit_length, :digits, :numerator, :denominator, :rationalize, :gcd, :lcm, :gcdlcm, :next, :div, :|, :~, :imag, :abs2, :+@, :phase, :to_c, :polar, :angle, :conjugate, :conj, :eql?, :singleton_method_added, :i, :real?, :zero?, :nonzero?, :finite?, :infinite?, :step, :positive?, :negative?, :clone, :dup, :arg, :quo, :rectangular, :rect, :real, :imaginary, :between?, :clamp, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_set, :protected_methods, :instance_variables, :instance_variable_get, :private_methods, :public_methods, :public_send, :method, :public_method, :singleton_method, :define_singleton_method, :extend, :to_enum, :enum_for, :=~, :!~, :respond_to?, :freeze, :object_id, :send, :display, :nil?, :hash, :class, :singleton_class, :itself, :yield_self, :then, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :equal?, :!, :instance_exec, :!=, :instance_eval, :__id__, :__send__]
类别描述示例
Integer整数3
Fixnum正常数字1
Bignum大数字9999999910129
Float十进制浮点数3.0
Complex复数/虚数4+3i
Rational有理数9/4
BigDecimal有精度十进制数6.0

注意:在Ruby 2.4及之后的版本中,Fixnum和Bignum已经合并统一为Integer,也不再对用户暴露fixnum和bignum类型

所有数值类型继承自Numeric类,它们的继承关系如下

数值继承关系

重复指令

在Ruby中,可以使用数值的.times方法来摆脱传统for语句的繁文缛节

1
2
3
4
5
6
7
8
9
irb(main):003:0> 5.times do
irb(main):004:1* puts "one time!"
irb(main):005:1> end
one time!
one time!
one time!
one time!
one time!
=> 5

单独写出代码

1
2
3
5.times do
	puts "one time!"
end

6. 块

块是Ruby中经常使用的强大概念。 可将块视为一组捆绑在其他地方使用的指令的方式

块的开始和结束

  • 上文中的do/end风格总是被Ruby解释器所接受,do是块的开始,end是块的结束
  • 仅包含单个指令时,常使用{}来表示块的开始和结束,如
1
5.times {puts "one time!"}

块被传递给方法

使用块来做什么?块可以作为传递给方法的参数

5.times的后面,解释器看到了puts "one time!"的块,它就知道了“我要在5.times执行的每次运行这个块”

又例如:.gsub方法,在每次获得匹配时运行这个块

1
2
3
4
5
irb(main):006:0> "this is a sentence".gsub("e"){puts "Find an E!"}
Find an E!
Find an E!
Find an E!
=> "this is a sntnc"

可见,不是每次执行方法时都执行块,具体的执行规则因方法而异

在块被传递给方法时,也可以在管道字符中指定一个块参数:

1
2
3
5.times do |i|
	puts "time #{i}!"
end

输出结果如下:

1
2
3
4
5
time 0!
time 1!
time 2!
time 3!
time 4!

什么值会被放入块参数,也取决于要调用的方法

再看一个例子:

1
2
irb(main):012:0> "this is a sentence".gsub("e"){|letter| letter.upcase}
=> "this is a sEntEncE"

可以看到,.gsub方法放入块参数的是原始串替换后的结果

7. 数组

  • 与C语言等不同的是,Ruby的数组包含在方括号、而不是花括号之中
  • 可以通过“铲子运算符”<<(注意方向)添加一个元素到数组末尾
  • 使用方括号,在指定索引位置获取元素
  • 数组同样也是对象,可以使用一些方便的方法

下面是一些例子:

1
2
3
4
5
6
7
8
9
10
11
irb(main):016:0> animals = ["dogs", "cats", "birds"]
=> ["dogs", "cats", "birds"]

irb(main):017:0> animals << "tigers"
=> ["dogs", "cats", "birds", "tigers"]

irb(main):018:0> animals[1]
=> "cats"

irb(main):019:0> animals.last
=> "tigers"

常用数组方法

.sort:升序排序,数值按大小,字符串按照字典序排序

1
2
3
4
5
6
7
8
9
10
irb(main):020:0> array1=[1,4,3,5,2]
=> [1, 4, 3, 5, 2]
irb(main):021:0> array1.sort
=> [1, 2, 3, 4, 5]
irb(main):022:0> array1
=> [1, 4, 3, 5, 2]
irb(main):023:0> array2=["cat","bird","tiger"]
=> ["cat", "bird", "tiger"]
irb(main):024:0> array2.sort
=> ["bird", "cat", "tiger"]

可以看到,排序的功能体现在返回的数组,而原本的数组不会被改变

.each:返回数组中每一个元素

1
2
3
4
5
array2.each
=> #<Enumerator: ["cat", "bird", "tiger"]:each>

5.times
=> #<Enumerator: 5:times>

可以看到,返回的类型是枚举器(Enumerator),代码5.times返回的也是一个枚举器,我们可以很自然地像使用5.times那样使用块来完成更多的操作;并且当数组的每个内容作为块参数的时候,我们可以很方便地遍历数组,从而完成一些操作

1
2
3
4
irb(main):027:0> array2.each { |content| puts content}
cat
bird
tiger

当然,我们可以配合前文的.sort方法实现有序遍历

1
2
3
4
5
irb(main):029:0> array2.sort.each {|content| puts content}
bird
cat
tiger
=> ["bird", "cat", "tiger"]

.suffle:打乱数组并返回随机打乱后的数组,和.sort一样,不对原数组产生影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
irb(main):001:0> array=[1,4,3,2]
=> [1, 4, 3, 2]
irb(main):004:0> array.shuffle
=> [1, 3, 4, 2]
irb(main):005:0> array.shuffle
=> [4, 2, 1, 3]
irb(main):006:0> array.shuffle
=> [3, 1, 2, 4]
irb(main):007:0> array.shuffle
=> [1, 4, 2, 3]
irb(main):008:0> array.shuffle
=> [4, 2, 1, 3]
irb(main):009:0> array.shuffle
=> [4, 2, 1, 3]
irb(main):010:0> array.shuffle
=> [1, 2, 4, 3]
irb(main):011:0> array
=> [1, 4, 3, 2]

.collect.map:返回另外一个相同的数组,这一点在某些情境下很好用

可以认为collect和map是一个东西的两个名字

在Ruby中不能直接将运算符应用于数组(除数组*数字的形式,这会按照原来数组的内容连续复制指定的次数,并返回的结果)

1
2
3
4
5
6
7
8
9
10
11
irb(main):047:0> [1,2,3]*3
=> [1, 2, 3, 1, 2, 3, 1, 2, 3]

irb(main):048:0> 3*[1,2,3]
Traceback (most recent call last):
        5: from /usr/bin/irb:23:in `<main>'
        4: from /usr/bin/irb:23:in `load'
        3: from /usr/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        2: from (irb):48
        1: from (irb):48:in `*'
TypeError (Array can't be coerced into Integer)

若要对每个元素乘3并返回得到的数组,使用collect/map即可

1
2
3
4
5
6
7
8
9
10
11
irb(main):049:0> [1,2,3].collect{|n| n*3}
=> [3, 6, 9]

irb(main):050:0> [1,2,3].collect
=> #<Enumerator: [1, 2, 3]:collect>

irb(main):051:0> [1,2,3].map
=> #<Enumerator: [1, 2, 3]:map>

irb(main):052:0> [1,2,3].map{|n| n*3}
=> [3, 6, 9]

可以看出.collect返回的是一个枚举器,通过将块作为参数传入方法,返回值由块中的运算结果决定,返回值会加入到一个数组中返回

可以看到 .collect.each有相似之处:直接调用时它们都返回一个迭代器,但是迭代器的类型有区别,

1
2
3
4
5
irb(main):061:0> [1,2,3].each
=> #<Enumerator: [1, 2, 3]:each>

irb(main):062:0> [1,2,3].collect
=> #<Enumerator: [1, 2, 3]:collect>

传入一个块看看:

1
2
3
4
5
irb(main):064:0> [1,2,3].each {|n| n*3}
=> [1, 2, 3]

irb(main):065:0> [1,2,3].collect {|n| n*3}
=> [3, 6, 9]

可以看到each返回的是原本的数组,而collect返回的是经过块处理过的数组,是一个新的数组,原来的数组同样不受影响

collect!/map!就有一些微妙的区别了,collect!返回的是原本的数组,所有的修改也会在原来的数组上进行体现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
irb(main):014:0> arr.object_id
=> 70368459723060
irb(main):015:0> arr.map!{|n| n*3}.object_id
=> 70368459723060

irb(main):024:0> arr
=> [1, 2, 3]
irb(main):025:0> arr.map{|n| n*3}
=> [3, 6, 9]
irb(main):026:0> arr
=> [1, 2, 3]
irb(main):027:0> arr.map!{|n| n*3}
=> [3, 6, 9]
irb(main):028:0> arr
=> [3, 6, 9]

8. 哈希

哈希是键值对的集合,使用可以找到,即按照键来寻址。键的数据类型比较多元,可以使用数值、字符串、甚至是数组当做键。由于键值对的存储方式,哈希的语法也相对更加复杂,需要多加使用以习惯。

以下是创建、读取、修改的语法

1
2
3
4
5
6
irb(main):048:0> hash1 = {"small"=>1, "middle"=>2, "large"=>3}
=> {"small"=>1, "middle"=>2, "large"=>3}
irb(main):049:0> hash1["small"]
=> 1
irb(main):050:0> hash1["ultra"]=5
=> 5

同一个哈希中,键的数据类型可以不同

1
2
3
4
irb(main):051:0> hash1[6]=5
=> 5
irb(main):052:0> hash1
=> {"small"=>1, "middle"=>2, "large"=>3, "ultra"=>5, 6=>5}

.keys返回所有键的数组,.values返回所有值的数组

1
2
3
4
irb(main):053:0> hash1.keys
=> ["small", "middle", "large", "ultra", 6]
irb(main):054:0> hash1.values
=> [1, 2, 3, 5, 5]

简化的哈希语法(>=Ruby1.9)

当所有的键都是符号的时候,可以使用一个简化的语法

1
2
3
4
irb(main):055:0> hash2={small:1, middle:2, large:3}
=> {:small=>1, :middle=>2, :large=>3}
irb(main):056:0> hash2[:small]
=> 1

9.条件语句

即if-elseif-else结构,示例如下

1
2
3
4
5
6
7
8
9
if minutes < 7
    puts "The water is not boiling yet."
  elsif minutes == 7
    puts "It's just barely boiling"
  elsif minutes == 8
    puts "It's boiling!"
  else
    puts "Hot! Hot! Hot!"
end

Ruby使用C语言风格的逻辑运算符&& ||

Ruby的条件运算符主要有==!=<>>=<=

10. Nil

在Ruby中,一般意义上的”null”被表示为”nil”,同样需要与”0”区分开来

11. 对象、属性和方法

Ruby是完全面向对象的语言,自然存在类与对象的概念,在与虚拟机内部进行交互时,每一条数据都是对象;对象保存的信息被称为属性,对象的方法可以被执行

类与实例

定义类

看下面的例子:

1
2
3
4
5
6
7
class Student
  attr_accessor :first_name, :last_name, :primary_phone_number

  def introduction
    puts "Hi, I'm #{first_name}!"
  end
end

以上代码定义了一个名为Student的类,这个类包含属性first_name, :last_name, :primary_phone_number,还有一个方法 introduction

attr_accessor用于定义类实例的属性

创建类的实例

使用类名.new的形式来返回一个类的实例

1
lihua = Student.new

上述代码中,创建了一个名为lihua的实例变量

调用类方法

使用实例变量.方法名的形式即可调用类的方法:

1
lihua.introduction

对于有参数的方法,调用示例(假设这里introduction有参数):

1
lihua.introduction("Hi!")

方法的返回值

Ruby中,每调用一次方法都会返回一个值,默认返回其评估求值的最后一个表达式的值

例如:

1
2
3
4
5
6
irb(main):001:0> def return_a_number
irb(main):002:1> 1+1
irb(main):003:1> end
=> :return_a_number
irb(main):004:0> return_a_number
=> 2

五、 补充知识

如果上面的内容对于快速入门有些单薄,不妨阅读以下内容作为补充

1. 补充:控制语句

  • if-else
  • case
  • for
  • while/loop
  • until
  • break/next
  • redo/retry

1.1 if-else

这是高级程序设计语言中最基本的控制语句,在Ruby中逻辑也别无二致,需要注意的只是使用形式,下面给出一个例子

1
2
3
4
5
6
7
8
9
if minutes < 7
    puts "The water is not boiling yet."
  elsif minutes == 7
    puts "It's just barely boiling"
  elsif minutes == 8
    puts "It's boiling!"
  else
    puts "Hot! Hot! Hot!"
end

另外也是一个很常见的实现if-else逻辑功能的三目运算符? :,在Ruby中同样也能实现简单的if-else逻辑:

1
a = (var > 3 ? true : false); 

1.2 case

相对于C语言的switch-case语句,Ruby中发挥同样功能的case语句将switch替换为case、将case替换为when并在此基础上去掉{}使用end标识,:,break,具体请看下方示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case day   
    when "2"   
      puts 'Wear Red'   
    when "3"   
      puts 'Wear Green'   
    when "4"   
      puts 'Wear Yellow'   
     when "5"   
      puts 'Wear White'   
     when "6"   
      puts 'Wear Black'   
    else   
      puts "Wear Any color"   
end

注意:Ruby的case语句”默认break”,一些传统switch-case语句的实现在这里可能有所不同

1.3 for

for语句也有相对的语法简化,很有“年轻”语言的风格

语法如下:

1
2
3
for variable [, variable ...] in expression [do]  
   code  
end

使用for遍历范围

一个例子

1
2
3
4
5
puts "输入一个数字:"
a = gets.chomp.to_i   
for i in 1..a do   
  puts i   
end

使用for遍历数组

一个例子

1
2
3
4
x = ["Blue", "Red", "Green", "Yellow", "White"]   
for i in x do   
  puts i   
end

1.4 while/loop

Ruby中while和loop实现的功能对应于C语言中的while和do-while语句

while语句的语法如下

1
2
3
while conditional [do]  
   code  
end

代码示例

1
2
3
4
5
6
7
8
#!/usr/bin/ruby   

puts "Enter a value:" 
x = gets.chomp.to_i   
while x >= 0    
  puts x   
  x -=1   
end

loop语句的语法如下

1
2
3
4
loop do   
  #code to be executed  
  break if booleanExpression  
end

代码示例

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/ruby   

loop do   
  puts "Checking for answer: "   
  answer = gets.chomp   
  if answer == '5'   
    puts "yes, I quit!"
    break   
  end   
end

1.5 until

until语句站在了while语句的对立面:until在条件语句为假时循环,而while在条件语句为真时循环

语法如下

1
2
3
until conditional  
   code  
end

不难想到,我们只需要一个!就可以轻松转换两种语句,但很多时候!会降低一些程序的易读性,把until的特性运用到合适的情景中,往往会让逻辑更加清楚,例如:

1
2
3
4
until i == 10   
    print i, "\n"   
    i += 1   
end
  • until逻辑:“我们将i输出后递增,直到i等于10为止”
  • while逻辑:“当i不等于10的时候,我们将i输出后递增”

很明显,在这种情景下,until语句更接近人类的思维逻辑,也更加易读

1.6 break/next

break和常见的break基本一致,常用来跳出当前的循环

示例如下

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/ruby   

i = 1   
while true   
    if i*5 >= 25   
        break   
    end   
    puts i*5   
    i += 1   
end

next和常见语言的continue一致,用于直接进入下一次循环

1
2
3
4
5
6
7
8
#!/usr/bin/ruby   
for i in 1...12   
   if (i % 3 ==0) then   
      puts "Skip over ... "
      next   
   end   
   puts i   
end

1.7 redo/retry

redo可以当做不进入循环条件的next,即在不判断条件的情况下,进入下一个循环

由于redo不判断循环条件,如果不对redo加以限制,非常容易进入死循环:

1
2
3
4
5
6
5.times do 
  puts "hello!"
  redo
end

#程序进入死循环,不断输出"hello!"

解决办法是加入条件语句:

  • 在redo外写if
  • 在redo后写if
1
2
3
4
5
6
7
8
9
10
11
#在redo外写if

5.times do |i|
  puts"hello!"
  i+=1
  if i ==3
    redo
  end
end

##输出比没有redo多一个,不会进入死循环
1
2
3
4
5
6
7
8
9
#在redo后写if

5.times do |i|
  puts"hello!"
  i+=1
  redo if i==3
end

##输出比没有redo多一个,不会进入死循环

两种写法是等价的,推荐后一种写法,更加简洁易读

关于retry:

Ruby1.9以及之后的版本不支持在循环中使用retry。

2. 补充:运算符

  • 算术运算符
  • 按位运算符
  • 逻辑运算符
  • 赋值运算符
  • 比较运算符
  • 范围运算符

2.1 算术运算符

运算符描述
+加法运算符
-减法运算符
*乘法运算符
/除法运算符
**指数运算符
%取余运算符

2.2 位运算符

运算符描述
&位与运算符
|位或运算符
<<左移运算符
>>右移运算符
^异或运算符
~补码运算符

2.3 逻辑运算符

运算符描述
&&逻辑与
||逻辑或
逻辑非

2.4 赋值运算符

运算符描述
=简单赋值运算符
+=相加并赋值运算符
-=相减并赋值运算符
*=相乘并赋值运算符
/=相除并赋值运算符
%/模除并赋值运算符
**=指数幂并赋值运算符

2.5 比较运算符

运算符描述
==等于运算符
!=不等于运算符
>大于运算符
<小于运算符
>=大于等于运算符
<=小于等于运算符
<=>组合比较运算符
.eql检查比较操作数相等和类型是否相同
equal?检查比较对象ID是否相同

2.6 范围运算符

范围运算符创建一系列连续的值,包括两者之间的值的起始,结束和范围。 ..创建一个范围,包括最后一个术语,...创建一个不包括最后一项的范围。 例如,对于1..5的范围,输出范围为1到5,范围值也就是:1,2,3,4,5。 而对于1...5的范围,输出范围为1到4,范围值也就是:1,2,3,4。

3. 补充:变量

  • 局部变量
  • 实例变量
  • 类变量
  • 全局变量

下面分别通过命名方式和作用范围两房变,对这四种变量进行区分

命名方式

局部变量以下划线或小写字母开头解释器解释到变量所在语句时才在内存中存在
实例变量@开头此处的实例是类对象的本身(类的对象),可以不赋值,默认为nil
类变量@@开头需要初始化后才能地调用
全局变量$开头默认初始化为nil

作用范围

局部变量:只作用于本类、本方法或本模块内。需要注意的、同java很大的一个区别是:Ruby在类中定义局部变量,就只能在类中访问,其子类、方法、内部类都不能够调用

查看示例:

1
2
3
4
5
6
class Account
  user=User.new
  def myMethod
    puts user.name  #调用发生错误:user未定义
  end
end

实例变量:类中任意访问,类外通过方法访问,作用于本实例对象范围内、或本类的实例对象范围内

请注意:

  • Ruby完全面向对象,所以类(Account)也是一个对象
  • 类(Account)是class类的一个对象
  • 类实例化后的对象(Account.new)是Account类的一个对象
  • 所以存在两个不同的域:定义在Account中的变量不能在Account.new中访问,反之而亦然

查看示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A   
    @alpha='This is @alpha\' value!' 
   
    def A.look
        puts "#@alpha"  
    end
    def look 
        @alpha='This is @alpha\' value from look!' 
        puts "#@alpha"  
    end
    def look_again  
        puts "#@alpha"  
    end
end  
   
  A.look        #输出:'This is @alpha' value!''
  a=A.new
  a.look        #输出:'This is @alpha' value from look!'
  a.look_again  #输出:'This is @alpha' value from look!'
  • 可见@alpha='This is @alpha\' value!' 是定义在类对象本身的实例变量
  • @alpha='This is @alpha\' value from look!'是定义在类实例化后的对象中的实例变量

另外,使用实例变量还应该注意以下几点:

  • 实例对象不管在类中定义、还是在方法中定义,都属于类,而不属于方法
  • 实例变量只存在于实例范围,不可在子类中引用或者赋值
  • 实例变量永远是private的,若想在外部访问,需要使用方法
  • 实例变量可以使用attr_accessor简便定义

类变量:如@@user,作用于类的所有范围,所有实例共享,包括子类及其实例对象,类变量通过protected声明

查看示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A  
  @@alpha='This is @alpha\' value!' 
  def A.look
    puts "#@@alpha"  
  end
  def look 
    puts "#@@alpha"  
  end
  def look_again  
    puts "#@@alpha"  
  end
end  
 
class B<A
end
 
A.look        #输出:'This is @alpha' value!'
B.look        #输出:'This is @alpha' value!'
a=A.new
a.look        #输出:'This is @alpha' value!'
a.look_again  #输出:'This is @alpha' value!'
b=B.new
b.look        #输出:'This is @alpha' value!'

全局变量:如$user,尽量少使用 ,作用于整个程序的生命周期,常驻内存,过量使用会引起性能下降,内存溢出,Ruby内置一些全局变量,获取相关数据十分方便,如$0’代表的是所运行应用程序的文件名称,$:代表的是默认的文件搜索路径;$$代表的是 ruby 程序的进程id

查看示例:

1
2
3
4
5
6
7
8
9
10
11
#!usr/bin/ruby

class A
  def display
    puts "$0 is: #{$0}"
    puts "$: is: #{$:}"
    puts "$$ is: #{$$}"
  end
end
s=A.new
s.display

输出结果:

1
2
3
$0 is: t.rb
$: is: ["/usr/share/rubygems-integration/all/gems/did_you_mean-1.2.0/lib", "/usr/local/lib/site_ruby/2.6.0", "/usr/local/lib/x86_64-linux-gnu/site_ruby", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/2.6.0", "/usr/lib/x86_64-linux-gnu/ruby/vendor_ruby/2.6.0", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/2.6.0", "/usr/lib/x86_64-linux-gnu/ruby/2.6.0"]
$$ is: 118

六、 相关链接

This post is licensed under CC BY 4.0 by the author.

将"用 Sublime Text3 打开"加入右键菜单

将"Open with Code"加入右键菜单