数据类型

Ruby核心库(core,不包括标准库)的数据类型分为:

  • 数字(Numeric -> 包含了Integer/Float子类, Integer又包含了Fixnum和Bignum子类)
  • 字符串(String)
  • 符号 (Symbol)
  • 数组 (Array)
  • 哈希 (Hash)
  • 范围 (Range)

数字Numeric

数字,分为很多种,整数、小数。 Ruby中数字类型很简单:

1.class #=> Fixnum
1.class.superclass #=> Integer
1.class.superclass.superclass #=> Numeric

一个数字字面量,即为一个数字类型,ruby中一切皆对象,所以这个数字字面量也可以响应一个叫class的方法,来返回它自己的类别,是Fixnum。 superclass方法,则返回一个类的父类。

Ruby允许我们像上面代码那样,连续调用,「返回的值」即为下一次调用的「消息接收者」,当然你得保证返回的值是可以响应那个消息的。

还有小数:

0.1.class #=> Float
0.1.class.superclass #=> Numeric

还有比较大的整数呢。

(2**200).class #=> Bignum
(2**200).class.superclass #=> Integer

#  当值很大的时候
(2**2000000000)   #=> 返回一个Infinity,代表无穷大
(2**2000000000).class #=> Float

结论:

Ruby中整数的值在一个小范围的时候,是Fixnum类型, 当数值很大的时候,则变成了Bignum类型,太大的值会返回一个固定的值Infinity,代表无穷大。而这个Infinity是一个Float对象。

Ruby中的小数, 是一个Float类型。 Float类型在Ruby中算是一个缺陷,因为它在计算过程中会产生误差,会带来一些bug。实际应用中都不会去使用这个数字类型。我们一般用Ruby标准库中的BigDecimal类型。

BigDecimal

示例:


require 'bigdecimal'

sum = BigDecimal.new("0")
10_000.times do
  sum = sum + BigDecimal.new("0.0001")
end
print sum #=> 0.1E1

因为是Ruby标准库,使用的时候一定要require那个库。

字符串(String)

Ruby的字符串,分为两种。一种是双引号包含的东西,一种是单引号所包含的东西。


str = " hello world "
str = ' hello world '

他们的区别,就是单引号的字符串,基本是原样输出,只能识别'\''这样形式的转义符。而双引号则完全识别转义符。

str = '\'' #=> "'"
str = "\'" #=> "'"

str = 'hello \n \t world!' #=> "hello \\n \\t world!"
str = "hello \n \t world!" #=> "hello \n \t world!"

#{}

使用#{}可以把一个变量,「镶嵌」到一个字符串里面:


hello = "hi"
str = " #{hello} world "  #=> "hi world"

str = '#{hello} world' #=> "\#{hello} world"

通过上面的代码,也可以看出来单引号字符串和双引号字符串之间的差别,就是单引号字符串,无法识别#{}这个操作符。

%q / %Q 操作符

对于一些比较复杂的字符串,像:

"#{name} said: \"Clap your hands!\""

这种字符串里面,双引号也作为了字符串的内容, 影响了可读性。 那么Ruby就提供了一个%Q操作符来帮助我们解决这样的问题:

name = 'Alex'
%Q|#{name} says: "Try ftp://ruby-lang.org/pub/ruby/1.9/"|

%Q-#{name} says: "Clap your hands!"-

%Q/#{name} says: "Play tic-tac-toe!"/

# 省略 Q

%-#{@name} says: "Clap your hands!"-
%/#{name} says: "Play tic-tac-toe!"/
%[#{name} says: "Play tic-tac-toe!"]

%Q后面可以跟任何一对对称的符号,只要是对称就可以。

%Q代表了双引号,当%后面省略了Q,也是一样的。 而%q, 则是代表单引号.

name = 'Lee'
%q[#{name} says: "Play tic-tac-toe!"] #=> "\#{name} says: \"Play tic-tac-toe!\""

符号 (Symbol)

符号类型,在Ruby中用一个冒号加名字或者是字符串来表示。

:name
:_name
:"name"

你不能使用冒号和数字来声明一个符号型,否则会报错。

:1
#=> SyntaxError: unexpected tINTEGER, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END

符号是什么

在Ruby中,符号表示一个「名字」。它有点类似于字符串,但和字符串又有不同。在Ruby中,一切皆对象,符号类型,也是对象,每个对象都有一个唯一的对象标识符object_id, 对象标识符一样,就代表是同一个对象,否则就是不同的对象。我们来比较下符号和字符串。


:name.object_id #=> 66088
:name.object_id #=> 66088
:name.object_id #=> 66088

"name".object_id #=> 10285860
"name".object_id #=> 10058700
"name".object_id #=> 8904020

从上面的代码里可以看出, 连续三个:name,都是指同一个Symbol对象,而连续三个"name",则是三个不同的对象。

所以,Symbol对象是一种具有唯一性的类「字符串」, 因为Symbol也拥有字符串的一些行为。比如:


:name.upcase #=> :NAME
"name".upcase #=> 'Name'

当然,不是全部的字符串方法它都可以响应,你可以去ruby-doc.org去比较他们的方法。

Symbol对象,从创建开始,一直到程序退出运行,都是被存放到一个叫做符号表的地方, Ruby的垃圾回收(GC)不会清理这些符号对象。而字符串不一样, GC会清理掉那些无用的字符串对象。

在认识到符号的这些特性之后, 应该不难理解我们把符号类型用来表示一个「名字」的概念。

举个例子:

有三个双胞胎字符串对象:'name', 'name', 'name', 我们可以用符号类型来形容他们的长相, 都是:name。

数组 (Array)

Ruby中的数组,是一个任何对象的有序的、用整数来索引的集合。

arr = [1, 2, :a, 'name', nil]
arr = Array[1, 2, 3, :name]
arr.class #=> Array
arr[0] #=> 1
arr[1] #=> 2
#...

Ruby的数组,可以存放任何对象。


arr = [1, 2, 3, [4, 5, 6]]

arr[0] #=> 1
arr[3][0] #=> 4

上面,我们定义了一个二维数组,通过[]方法,传入索引参数,可以取得数组的值。

数组的概念很容易理解。可以结合ruby-doc.org来查看并且练习数组中内建的很多方法。

哈希 (Hash)

Ruby中的哈希,是一个键值对的集合。在Ruby1.8中是无序的,但是在Ruby1.9开始,哈希变成有序的了。

h = {a: 100, b: 200}
h = Hash["a" => 1000, "b" => 2000]
h[:a] #=> 100
h['a'] #=> 1000

Hash是在{}中被包含的字面量,也可以通过Hash[]方法来创建一个hash,参考上面的代码。我们可以通过一个key来获取其所对应的值。

Hash的key,必须是唯一的。

h = {a: 1, b: 2, a: 3}

h[:a] #=> 3

key必须是唯一,所以上面的代码中,h[:a]取出的值是3.

我们提倡使用符号类型来作为Hash的key,这也是Ruby1.9开始引入下面的hash结构的原因之一:

h = {a: 1, b: 2}
#等同于
h = {:a => 1, :b => 2}

但也不是所有的情况都试用于上面第一种写法,比如你必须要以数字作为key,就只能用第二种形式。但是大部分情况,第一种写法已经够用了。 使用字符类型做为key,可以节省内存。因为上面我们讲过,字符串和符号的区别,就是每个字符串都是不同的对象,而Ruby中的对象是占用内存的,所以我们尽量让其少生成点对象。这也是Ruby鼓励我们在Hash中使用字符类型作为key的原因。

同样,Hash有很多内建方法,可以去ruby-doc.org中自行查看并且练习。

范围 (Range)

范围(Range)发生无处不在:一月至十二月,0到9,50至67行,依此类推。

(1..3) 和 (1...3) 都表示一个范围。

(1..3).class #=> Range
(1...3).class #=> Range

范围有一个起点,一个终点,产生连续的序列值。如果是两个点把起点和终点相连,则范围的连续值包含终点值。如果是三个点,则范围的连续值不包含终点值。

可以参考我们学过的数学中的开闭区间来理解这个概念。

(1..3).each{|i| puts i}
#=> 1
    2
    3

(1...3).each{|i| puts i}
#=> 1
    2

我们使用each方法来迭代范围中的值,可以看得出两个点和三个点范围类型的区别。

结语

Ruby中的主要数据类型大概就讲这么多,Ruby还有其他的数据类型,比如时间日期, 表示真假的Boolean型,甚至标准库中所包含的数据类型,这些大家可以参考ruby-doc.org去自行学习。