ZetCode

Ruby 数据类型

最后修改于 2023 年 10 月 18 日

在本 Ruby 教程中,我们将介绍数据类型。

包括电子表格、文本编辑器、计算器和聊天客户端在内的各种计算机程序都使用数据。使用各种数据类型的工具是现代计算机语言的重要组成部分。一个数据类型是一组值,以及对这些值允许的操作。

Ruby 数据类型列表

Ruby 有几种数据类型。所有数据类型都基于类。以下是 Ruby 中识别的数据类型

在下面的例子中,我们有所有重要的 Ruby 数据类型。

types.rb
#!/usr/bin/ruby

h = { :name => "Jane", :age => 17 }

p true.class, false.class
p "Ruby".class
p 1.class
p 4.5.class
p 3_463_456_457.class
p :age.class
p [1, 2, 3].class
p h.class

我们打印它们的类名。一个类是一个模板,从这个模板创建每个对象。

p true.class, false.class

布尔值由 true 和 false 对象表示。

p "Ruby".class

这是一个字符串。

p 1.class
p 4.5.class
p 3_463_456_457.class

这些是数字。

p :age.class

这是一个符号,Ruby 独有的数据类型。

p [1, 2, 3].class
p h.class

这两个是容器,数组和哈希。

$ ./types.rb
TrueClass
FalseClass
String
Integer
Float
Integer
Symbol
Array
Hash

该程序列出了属于 Ruby 数据类型的类。

Ruby 布尔值

我们的世界中存在着二元性。有天堂和地球,水和火,阴和阳,男人和女人,爱和恨。这是我们存在的“布尔”性质。在 Ruby 中,布尔数据类型可以有两个值之一:true 或 false。布尔值是一种基本数据类型:它在计算机程序中非常常见。

快乐的父母正在等待孩子的出生。他们为这两种可能性都选择了名字。如果将是一个男孩,他们选择了约翰。如果将是一个女孩,他们选择了维多利亚。

random_name.rb
#!/usr/bin/ruby

# random_name.rb


male = [true, false].sample

if male
    puts "We use name John"
else
    puts "We use name Victoria"
end

该程序使用随机数生成器来模拟我们的情况。

male = [true, false].sample

使用 sample,我们从数组中随机选择一个值。

if male
    puts "We use name John"
else
    puts "We use name Victoria"
end

根据 male 变量,我们打印一条消息。如果 male 变量设置为 true,我们选择名字 John。否则,我们选择名字维多利亚。像 if/else 语句这样的控制结构使用布尔值。

$ ./random_name.rb
We use name Victoria
$ ./random_name.rb
We use name Victoria
$ ./random_name.rb
We use name John
$ ./random_name.rb
We use name John
$ ./random_name.rb
We use name John

多次运行程序。

Ruby 符号

符号用于表示其他对象。使用符号而不是字符串可能会节省一些资源。符号是 Symbol 类的实例对象。符号是通过在标识符前使用冒号生成的,例如 :name。一些对象也有 to_sym 方法。这些方法将这些对象转换为符号。

Ruby 符号不能在运行时更改。Ruby 符号经常用作哈希键,因为我们不需要字符串对象的完整功能来作为键。

symbols.rb
#!/usr/bin/ruby

p :name
p :name.class
p :name.methods.size
p "Jane".methods.size

p :name.object_id
p :name.object_id
p "name".object_id
p "name".object_id

在第一个例子中,我们展示了 Ruby 符号的一些基本操作。

p :name
p :name.class

我们将一个符号及其类打印到控制台。符号的类是 Symbol

p :name.methods.size
p "Jane".methods.size

我们比较与符号和字符串实例关联的方法的数量。一个字符串的方法数量是符号的两倍多。

p :name.object_id
p :name.object_id
p "name".object_id
p "name".object_id

相同的符号具有相同的 id。相同的字符串具有不同的 id。

$ ./symbols.rb
:name
Symbol
86
183
71068
71068
60
80

这是一个示例输出。

符号可以用作标志。常量也可以用于这种情况。在 C/C++ 中,我们将使用枚举。

symbols2.rb
#!/usr/bin/ruby

light = :on

if light == :on
    puts "The light is on"
else
    puts "The light is off"
end

light = :off

if light == :on
    puts "The light is on"
else
    puts "The light is off"
end

一盏灯要么开要么关。对于这两种状态,我们可以定义符号。

light = :on

灯亮了。

if light == :on
    puts "The light is on"
else
    puts "The light is off"
end

程序的逻辑取决于 light 变量的状态。

符号经常用作哈希容器中的键。它们比字符串更有效。

symbols3.rb
#!/usr/bin/ruby

domains = {:sk => "Slovakia", :no => "Norway", :hu => "Hungary"}

puts domains[:sk]
puts domains[:no]
puts domains[:hu]

在脚本中,我们有一个 domains 哈希。哈希中的键是符号。

puts domains[:sk]
puts domains[:no]
puts domains[:hu]

键用于访问哈希的值。这里我们打印哈希的三个值。

$ ./symbols3.rb
Slovakia
Norway
Hungary

Ruby 解释器在内部将一些引用存储为符号。

symbols4.rb
#!/usr/bin/ruby

class Being

    def initialize
        @is = true
    end

    def say
        "I am being"
    end
end

b = Being.new

p b.method :say
p b.instance_variable_get :@is

定义了一个 Being 类。该类有一个自定义实例变量 @is 和一个 say 方法。这两个实体使用 Ruby 的符号存储。

p b.method :say

method 方法查找具有给定名称的接收器方法在 b 对象中。我们查找 :say 符号。

p b.instance_variable_get :@is

我们使用 instance_variable_get 来检查 @is 是否是 b 对象的实例变量。在内部,该变量存储为 :@is 符号。

$ ./symbols4.rb
#<Method: Being#say() ./symbols4.rb:9>
true

所有符号都存储在符号表中。在下一个例子中,我们查看该表。 Symbol 类的 all_symbols 方法返回符号表中的所有符号的数组。

symbols5.rb
#!/usr/bin/ruby

def info
  "info method"
end

@v = "Ruby"

class Some
  @@n = "16"
end

p Symbol.all_symbols.include? :info
p Symbol.all_symbols.include? :@v
p Symbol.all_symbols.include? :@@n

在 Ruby 脚本中创建了一个方法、一个实例变量和一个类变量。我们检查这些实体是否存储在符号表中。

p Symbol.all_symbols.include? :info

我们检查 :info 符号是否在符号表中。该行返回 true。

$ ./symbols5.rb
true
true
true

所有三个符号都存在于 Ruby 符号表中。

Ruby 整数

整数是实数的一个子集。它们在没有分数或十进制分量的情况下被写入。整数属于集合 Z = {..., -2, -1, 0, 1, 2, ...} 这个集合是无限的。

在计算机语言中,整数是基本数据类型。实际上,由于计算机的有限容量,计算机只能处理整数值的子集。整数用于计算离散实体。我们可以有 3、4 或 6 个人,但我们不能有 3.33 个人。我们可以有 3.33 千克。

与 Java 或 C 等语言不同,Ruby 中的整数是对象。

integers.rb
#!/usr/bin/ruby

p -2
p 121
p 123265
p -34253464356
p 34867367893463476

p 1.class
p 23453246.class
p 234532423563456346.class
p 2345324235632363463456456346.class

p 5 / 2
p 5.div 2

在这个例子中,我们处理整数。

p -2
p 121
p 123265
p -34253464356
p 34867367893463476

这些是各种大小的正整数和负整数值。

p 1.class
p 23453246.class
p 234532423563456346.class
p 2345324235632363463456456346.class

我们打印这些整数的类。

p 5 / 2
p 5.div 2

这两行显示了整数除法。当使用整数除法运算符/方法除以两个整数时,结果也是一个整数。

$ ./integers.rb
-2
121
123265
-34253464356
34867367893463476
Integer
Integer
Integer
Integer
2
2

整数可以用不同的符号在 Ruby 中指定:十进制、十六进制、八进制和二进制。十进制数通常使用,就像我们知道的那样。十六进制数以 0x 字符开头,八进制数以 0 字符开头,二进制数以 0b 字符开头。

inotations.rb
#!/usr/bin/ruby

puts 122
puts 0x7a
puts 0172
puts 0b1111010

在代码示例中,我们以所有这些符号打印十进制 122。

$ ./inotations.rb
122
122
122
122

示例的输出。

如果我们处理整数,我们就是在处理离散实体。我们会用整数来计数苹果。

apples.rb
#!/usr/bin/ruby

baskets = 16
apples_in_basket = 24

total = baskets * apples_in_basket

puts "There are total of #{total} apples"

在我们的程序中,我们计算苹果的总量。我们使用整数。

$ ./apples.rb
There are total of 384 apples

程序的输出。

大数字难以阅读。如果我们有一个像 245342395423452 这样的数字,我们发现很难快速阅读。在计算机外部,大数字用空格或逗号分隔。为了提高可读性,Ruby 允许整数包含下划线。 Ruby 解释器会忽略整数中的下划线。

underscore.rb
#!/usr/bin/ruby

p 23482345629
p 23_482_345_629

p 23482345629 == 23_482_345_629

该示例演示了下划线的使用。

p 23482345629 == 23_482_345_629

这行表明这两个数字是相等的。它打印 true。

$ ./underscore.rb
23482345629
23482345629
true

Ruby 浮点数

浮点数表示计算中的实数。实数衡量连续量,如重量、高度或速度。在 Ruby 中,十进制数是 FloatBigDecimal 类的对象。 BigDecimal 类是 Ruby 核心类,是 Ruby 标准库的一部分。此外,我们也可以使用 Rational 对象。

我们需要理解的是,十进制数是不精确的。 Ruby 官方文档明确指出,float 对象表示不精确的实数。

decimals.rb
#!/usr/bin/ruby

p 15.4
p 0.3455
p -343.4563

p 12.5.class
p -12.5.class
p (5.0 / 2).class

p 5.fdiv 2
p 12.to_f

在上面的程序中,我们使用浮点值。

p 15.4
p 0.3455
p -343.4563

这里我们打印了三个十进制值。十进制数有一个小数点字符。

p 12.5.class
p -12.5.class
p (5.0 / 2).class

上面的代码行显示了数字的类型。都是浮点数。在至少一个 Float 上应用整数除法也会产生一个 Float

p 5.fdiv 2
p 12.to_f

在这里,我们通过使用浮点 fdiv 除法方法和转换 to_f 方法来创建浮点值。

$ ./decimals.rb
15.4
0.3455
-343.4563
Float
Float
Float
2.5
12.0

默认情况下,十进制数显示小数点后最多 16 个数字。我们可以使用 sprintfprintf 方法控制浮点值的格式。

format_float.rb
#!/usr/bin/ruby

p 1/3.0
p 1.fdiv 2

puts sprintf "%.4f" % (1/3.0)
puts sprintf "%.7f" % (5/3.0)

格式化十进制数。

p 1/3.0
p 13.fdiv 4
p 1.fdiv 2

第一行打印一个小数点后有 16 个位置的十进制数。第二行打印小数点后两个数字,第三行打印一个数字。

puts sprintf "%.4f" % (1/3.0)
puts sprintf "%.7f" % (5/3.0)

在这里,我们使用 sprintf 方法控制小数点后的值数。 sprintf 方法的格式说明符中有一个精度。它是在 % 字符后面的一个数字。 f 是一个转换说明符,表示我们正在处理浮点值。

$ ./format_float.rb
0.3333333333333333
3.25
0.5
0.3333
1.6666667

Ruby 支持对浮点值使用科学计数法。也称为指数计数法,它是一种编写过大或过小的数字的方式,以便于以标准十进制计数法书写。

scientific.rb
#!/usr/bin/ruby

p 1.2e-3
p 0.0012

p 1.5E-4
p 0.00015

该示例显示了两个用科学计数法编写的十进制数。

$ ./scientific.rb
0.0012
0.0012
0.00015
0.00015

正如我们已经说明的那样,浮点值略有不准确。对于许多计算,普通的浮点数就足够精确了:例如,如果我们的体重是 60 公斤还是 60.000023 公斤,这一点并不重要。对于其他计算,包括许多科学和工程应用,精度至关重要。

Ruby 的标准库中有一个 BigDecimal。这个类为非常大或非常精确的浮点数提供了任意精度。

big_decimal.rb
#!/usr/bin/ruby

require 'bigdecimal'

sum = 0

1000.times do
    sum = sum + 0.0001
end

p sum


sum = BigDecimal("0")

1000.times do
    sum = sum + BigDecimal("0.0001")
end

puts sum.to_s('F')
puts sum.to_s('E')

在这个简单的例子中,我们比较了 FloatBigDecimal 的精度。

require 'bigdecimal'

必须导入 BigDecimal 类。

sum = 0

1000.times do
    sum = sum + 0.0001
end

p sum

我们形成一个循环,在其中将一个小的浮点值添加到 sum 变量。最后,会有一个小的不准确性。

sum = BigDecimal("0")

1000.times do
    sum = sum + BigDecimal("0.0001")
end

我们对 BigDecimal 值做相同的循环。

puts sum.to_s('F')
puts sum.to_s('E')

总和以浮点和工程计数法打印。

$ ./big_decimal.rb
0.10000000000000184
0.1
0.1e0

输出表明,使用 BigDecimal 进行计算比使用 Floats 更精确。

假设一位 100 米短跑运动员跑了 9.87 秒。他的速度是多少公里/小时?

sprinter.rb
#!/usr/bin/ruby

distance = 0.1
time = 9.87 / 3600

speed = distance / time

puts "The average speed of a sprinter is #{speed} km/h"

在此示例中,必须使用浮点值。

distance = 0.1

100m 是 0.1 km。

time = 9.87 / 3600

9.87s 是 9.87/60*60 h。

speed = distance / time

为了获得速度,我们将距离除以时间。

$ ./sprinter.rb
The average speed of a sprinter is 36.474164133738604 km/h

Ruby 有理数

Ruby 支持有理数。有理数是一个精确的数字。使用有理数,我们避免了舍入误差。在 Ruby 中,有理数是 Rational 类的对象。我们可以通过一些对象的特殊 to_r 方法创建有理数。

有理数是可以表示为两个整数 a/b 的分数,其中 b!=0。由于 b 可以等于 1,因此每个整数都是一个有理数。

rationals.rb
#!/usr/bin/ruby

puts 2.to_r
puts "23".to_r
puts 2.6.to_r

p Rational 0
p Rational 1/5.0
p Rational 0.5

这个例子展示了一些有理数。

puts 2.to_r

这里我们使用 to_r 方法将 2 个整数转换为 2/1 有理数。

p Rational 0.5

我们使用 Rational 类创建一个有理数。

$ ./rationals.rb
2/1
23/1
5854679515581645/2251799813685248
(0/1)
(3602879701896397/18014398509481984)
(1/2)

Ruby nil 值

Ruby 有一个特殊的值 nil。它是一个值的缺失。 nilNilClass 的单例对象。只有一个 nil;我们不能再拥有它了。

nil_value.rb
#!/usr/bin/ruby

puts nil
p nil

p $val

p [1, 2, 3][4]

p $val1 == $val2

一个带有 nil 值的例子。

puts nil
p nil

我们将 nil 值打印到控制台。 puts 方法打印一个空字符串; p 方法打印“nil”字符串。

p $val

当我们引用未设置的全局变量时,我们得到 nil 值。

p [1, 2, 3][3]

在这行代码中,我们引用了三元素数组的第四个元素。我们得到 nil。 Ruby 中的许多方法都为无效值返回 nil

p $val1 == $val2

该行返回 true。这是 nil 值是 NilClass 的单例对象的结果。

$ ./nil_value.rb

nil
nil
nil
true

Ruby 字符串

一个字符串是在计算机程序中表示文本数据的数据类型。一个 Ruby 字符串是一系列 unicode 字符。一个字符串是 String 的实例。字符串字面量是用双引号或单引号括起来的字符。

字符串是一个非常重要的数据类型。它值得一个专门的章节。这里我们只包含一个小例子。

strings.rb
#!/usr/bin/ruby

p "Ruby"
p 'Python'

p "Ruby".size
p "Ruby".upcase

p 23.to_s

在这个程序中,我们使用 Ruby 字符串。我们使用 p 方法进行打印,因为我们在输出中看到了数据类型。

p "Ruby"
p 'Python'

我们将两个字符串字面量打印到终端。第一个字面量用双引号括起来,第二个字面量用单引号括起来。

p "Ruby".size
p "Ruby".upcase

这两行调用了两个字符串方法。 size 方法返回字符串的大小:在本例中为 4 个字符。 upcase 返回大写字母的字符串。

p 23.to_s

to_s 方法将一个整数转换为一个字符串。

$ ./strings.rb
"Ruby"
"Python"
4
"RUBY"
"23"

在输出中,我们看到了用引号括起来的字符串。这是使用 p 方法的结果。 printputs 方法不会这样做。

Ruby 数组和哈希

数组和哈希是对象的集合。它们将对象分组到一个地方。

数组是对象的有序集合。哈希是键值对的集合。我们为数组和哈希各有一个单独的章节。下面的例子只是快速了解这两个容器。

arrays_hashes.rb
#!/usr/bin/ruby

nums = [1, 2, 3, 4]

puts "There are #{nums.size} items in the array"

nums.each do |num|
    puts num
end


domains = { :de => "Germany", :sk => "Slovakia",
            :us => "United States", :no => "Norway" }

puts domains.keys
puts domains.values

这是 Ruby 数组和哈希的快速示例。

nums = [1, 2, 3, 4]

puts "There are #{nums.size} items in the array"

nums.each do |num|
    puts num
end

这些行创建了一个包含 4 个项目的数组。在第二行中,我们计算数组的项目并将它们合并到消息中。稍后,我们通过 each 方法遍历数组,并将每个元素打印到控制台。

domains = { :de => "Germany", :sk => "Slovakia",
            :us => "United States", :no => "Norway" }

puts domains.keys
puts domains.values

这里我们创建一个 Ruby 哈希。然后我们打印它的键和值。

$ ./arrays_hashes.rb
There are 4 items in the array
1
2
3
4
de
sk
us
no
Germany
Slovakia
United States
Norway

Ruby 转换

我们经常同时使用多种数据类型。将一种数据类型转换为另一种数据类型是编程中的常见工作。类型转换类型转换是指将一个数据类型的实体更改为另一个数据类型。转换有两种类型:隐式转换和显式转换。隐式类型转换,也称为强制,是编译器进行的自动类型转换。 Ruby 只有显式转换。

Ruby 有内置的转换方法,如 to_ito_sto_fKernel 模块有一些用于执行转换的公共方法,如 IntegerStringFloat。这些方法不应与 Ruby 类混淆。

conv_methods.rb
#!/usr/bin/ruby

p Array(1..6)
p Complex 6
p Float 12
p Integer "34"
p Rational 6
p String 22

这里我们展示了 Kernel 转换方法。

$ ./conv_methods.rb
[1, 2, 3, 4, 5, 6]
(6+0i)
12.0
34
(6/1)
"22"
conversions.rb
#!/usr/bin/ruby

p "12".to_i
p 12.5.to_i
p nil.to_i

p 12.to_f
p "11".to_f
p nil.to_f

在上面的例子中,我们展示了一些数值转换。一些Ruby对象有to_ito_f方法,分别将对象转换为整数和浮点数。

p "12".to_i
p 12.5.to_i
p nil.to_i

在这段代码中,我们将字符串、十进制数和nil转换为整数类型。

p 12.to_f
p "11".to_f
p nil.to_f

这三行代码将一个整数、字符串和nil转换为十进制数据类型的对象。

$ ./conversions.rb
12
12
0
12.0
11.0
0.0

第二个例子展示了一些字符串转换。

string_conv.rb
#!/usr/bin/ruby

p "12".to_i
p "13".to_f
p "12".to_r
p "13".to_c

p "Jane".to_sym

v = "Ruby Python Tcl PHP Perl".split
p v.class

在上面的例子中,我们将字符串转换为不同数据类型的对象。

p "12".to_i
p "13".to_f
p "12".to_r
p "13".to_c

这里,字符串被转换为整数、十进制数、有理数和复数。

p "Jane".to_sym

字符串变成一个符号。

v = "Ruby Python Tcl PHP Perl".split
p v.class

在这里,我们使用String类的split方法将字符串转换为数组。

$ ./string_conv.rb
12
13.0
(12/1)
(13+0i)
:Jane
Array

这就是我们得到的。

下一个小例子展示了数组和哈希的转换。

hash2array.rb
#!/usr/bin/ruby

h = {:de => "Germany", :sk => "Slovakia"}
p h.to_a

a = [:de, "Germany", :sk, "Slovakia",
     :hu, "Hungary", :no, "Norway"]
p Hash[*a]

在示例代码中,我们创建一个哈希并将其转换为数组。然后,我们创建一个数组并将其转换为哈希。

h = {:de => "Germany", :sk => "Slovakia"}
p h.to_a

使用to_a方法创建一个哈希并将其转换为数组。

a = [:de, "Germany", :sk, "Slovakia",
     :hu, "Hungary", :no, "Norway"]
p Hash[*a]

创建一个数组并将其转换为哈希。在这种情况下,星号是展开运算符。这是从Perl中借鉴的Ruby习惯用法之一。它将一个数组拆分成几个变量。

$ ./hash2array.rb
[[:de, "Germany"], [:sk, "Slovakia"]]
{:de=>"Germany", :sk=>"Slovakia", :hu=>"Hungary", :no=>"Norway"}

在Ruby教程的这一部分中,我们涵盖了数据类型及其转换。