ZetCode

Ruby 变量

最后修改于 2023 年 10 月 18 日

在本 Ruby 教程中,我们将更详细地研究变量。

一个 变量 是存储数据的地方。 每个变量都有一个唯一的名称。 变量名有一些命名约定。 变量保存对象。 更确切地说,它们引用位于计算机内存中的特定对象。 每个对象都具有特定数据类型。 有内置的数据类型,也有自定义构建的数据类型。

Ruby 属于动态语言家族。 与 Java、C 或 Pascal 等强类型语言不同,动态语言不会声明变量具有特定数据类型。 相反,解释器在赋值时确定数据类型。 Ruby 中的变量可以随时间包含不同的值和不同类型的值。

simple_variable.rb
#!/usr/bin/ruby

i = 5
puts i
i = 7
puts i

术语“变量”源于一个事实,即变量,与常量不同,可以随时间取不同的值。 在此示例中,有一个名为 i 的变量。 首先,它被赋值为 5,后来又被赋值为另一个值 7。

Ruby 变量命名约定

与其他任何编程语言一样,Ruby 对变量标识符有一些命名约定。

Ruby 是一种 大小写敏感 的语言。 这意味着 age 和 Age 是两个不同的变量名。 大多数语言都是大小写敏感的。 BASIC 是一个例外; 它是一种大小写不敏感的语言。 虽然我们可以通过更改字符的大小写来创建不同的名称,但不建议这样做。

case.rb
#!/usr/bin/ruby

i = 5
p i
I = 7
p I

代码示例定义了两个变量:Ii。 它们包含不同的值。

$ ./case.rb
5
7

Ruby 中的变量名可以由字母数字字符和下划线 _ 字符组成。 变量不能以数字开头。 这使得解释器更容易将文字数字与变量区分开来。 变量名不能以大写字母开头。 如果标识符以大写字母开头,则在 Ruby 中将其视为常量。

valid_variables.rb
#!/usr/bin/ruby

name = "Jane"
placeOfBirth = "Bratislava"
placeOfBirth = "Kosice"
favorite_season = "autumn"

n1 = 2
n2 = 4
n3 = 7

p name, placeOfBirth, favorite_season
p n1, n2, n3

在本脚本中,我们展示了一些有效的变量名。

变量名应该 有意义。 选择描述性名称作为变量是一个好的编程习惯。 这样程序会更具可读性。

meaningful.rb
#!/usr/bin/ruby

name = "Jane"
place_of_birth = "Bratislava"
occupation = "student"

i = 5
while i > 0 do
    puts name
    i -= 1
end

该脚本显示了三个描述性变量名。 与例如 pob 相比,place_of_birth 对程序员来说更具描述性。 通常认为在循环中选择简单的变量名是可以接受的。

Ruby 符

变量标识符也可以以特殊字符开头,也称为 。 符是附加到标识符的符号。 Ruby 中的变量符表示变量作用域。 这与 Perl 形成对比,Perl 中的符表示数据类型。 Ruby 变量符是 $@

sigils.rb
#!/usr/bin/ruby

tree_name = "pine"
$car_name = "Peugeot"
@sea_name = "Black sea"

class Animal
    @@species = "Cat"
end

p local_variables
p global_variables.include? :$car_name
p self.instance_variables
p Animal.class_variables

我们有四个具有不同作用域的变量。 作用域是引用变量的范围。 我们使用特殊的内置方法来确定变量的作用域。

tree_name = "pine"

没有符的变量是局部变量。 局部变量仅在局部有效:例如,在方法、块或模块内部。

$car_name = "Peugeot"

全局变量以 $ 字符开头。 它们在任何地方都有效。 应限制在程序中使用全局变量。

@sea_name = "Black sea"

@ 符开头的变量名是实例变量。 该变量在对象内部有效。

class Animal
    @@species = "Cat"
end

最后,我们有一个类变量。 该变量对特定类的所有实例都有效。

p local_variables

local_variables 给出一个在特定上下文中定义的所有局部变量的数组。 我们的上下文是 Ruby 顶层。

p global_variables.include? :$car_name

同样,global_variables 产生一个全局变量数组。 我们不会将所有全局变量打印到终端,因为它们有很多。 每个 Ruby 脚本都从一堆预定义的变量开始。 相反,我们调用数组的 include? 方法来检查我们的全局变量是否在数组中定义。 还要注意,我们使用它们的符号来引用变量。 (符号以冒号字符开头。)

p self.instance_variables

self 伪变量指向 instance_variables 方法的接收者。 在我们的例子中,接收者是 main,即 Ruby 顶层执行区域。

p Animal.class_variables

最后,我们有一个类变量数组。 mainAnimal 类的一个实例。

$ ./sigils.rb
[:tree_name]
true
[:@sea_name]
[:@@species]

我们看到了变量的符号名称。

Ruby 局部变量

局部变量是在 Ruby 源代码的局部区域内有效的变量。 该区域也称为 局部作用域。 局部变量存在于 Ruby 模块、方法、类的定义中。

locals.rb
#!/usr/bin/ruby

def method1
   x = 5
   p x
end

method1

p x

我们有一个名为 method1 的方法,它有一个变量。 该变量是局部的。 这意味着它仅在方法定义中有效。 我们只能在方法名称和 end 关键字之间引用 x 变量。

def method1
   x = 5
   p x
end

这是 method1 方法的定义。 在方法内部,我们创建一个局部 x 变量。 我们将变量的值打印到终端。

method1

该方法被调用。

p x

我们尝试在方法定义之外引用局部变量。 这将导致 NameError。 Ruby 解释器找不到这样的标识符。

$ ./locals.rb
5
Traceback (most recent call last):
./locals.rb:10:in `<main>': undefined local variable or method `x' for main:Object (NameError)

运行该示例会给出上述输出。

以下示例是前一个示例的稍作修改。

locals2.rb
#!/usr/bin/ruby

x = 5

def method1
    x = 10
    p x
end

method1

p x

我们有两个 x 变量。 一个在 method1 内部定义,另一个在外部定义。 它们是两个不同的局部变量。 它们彼此之间不会发生冲突。

x = 5

我们创建了一个局部 x 变量,它保存值 5。 该变量在主执行区域的局部作用域中有效。 它在 method1 内部无效。

def method1
    x = 10
    p x
end

method1 的定义内部,定义了一个新的局部变量 x。 它的值为 10。它存在于 method1 方法的主体中。 在 end 关键字之后,它不再存在。

$ ./locals2.rb
10
5

如果一个方法带参数,则会为每个参数创建一个局部变量。

parameters.rb
#!/usr/bin/ruby


def rectangle_area a, b

    puts local_variables
    return a * b
end

puts rectangle_area 5, 6

我们有一个方法定义,它接受两个值。 该方法返回一个矩形的面积。

def rectangle_area a, b
    puts local_variables
    return a * b
end

rectangle_area 方法有两个参数。 它们是一个矩形的边,我们为其计算面积。 将自动为标识符 a 和 b 创建两个局部变量。 我们调用 local_variables 方法来查看我们在该方法中使用了哪些局部变量。

puts rectangle_area 5, 6

在这里,我们传递两个值给方法 rectangle_area。 这些值将被分配给在该方法内部创建的两个局部变量。

$ ./parameters.rb
a
b
30

输出显示了三件事。 前两个是 rectangle_area 方法中局部变量的名称。 第三个是给定矩形的计算面积。

一个方法可以在另一个方法内部定义。 内部方法有它们自己的局部变量。

locals2.rb
#!/usr/bin/ruby

def method1

    def method2

        def method3

            m5, m6 = 3
            puts "Level 3"
            puts local_variables
        end

        m3, m4 = 3
        puts "Level 2"
        puts local_variables
        method3
    end

    m1, m2 = 3
    puts "Level 1"
    puts local_variables
    method2

end

method1

在这个 Ruby 脚本中,我们创建了三个方法。 method2method3 是内部方法。 method2method1 内部定义,method3method2 内部定义。 每个方法的局部变量只能在其定义的方法中访问。

$ ./locals2.rb
Level 1
m1
m2
Level 2
m3
m4
Level 3
m5
m6

从输出中我们可以看到 method1 有两个局部变量 m1m2。 内部的 method2 有局部变量 m3m4method3,最内部的方法,有局部变量 m5m6

本节的最后一个示例将展示几个局部作用域的演示。

locals3.rb
#!/usr/bin/ruby

module ModuleM
    m1, m2 = 4

    puts "Inside module"
    puts local_variables
end


def method1
    v, w = 3
    puts "Inside method"
    puts local_variables
end


class Some
    x, y = 2
    puts "Inside class"
    puts local_variables
end

method1

t1, t2 = 7

puts "Inside toplevel"
puts local_variables

在代码示例中,我们在模块、方法、类和顶层内部创建局部变量。 local_variablesKernel 模块的一个方法,它返回所有当前的局部变量。

module ModuleM
    m1, m2 = 4

    puts "Inside module"
    puts local_variables
end

模块是方法和常量的集合。 我们创建了两个局部变量 m1m2

def method1
    v, w = 3
    puts "Inside method"
    puts local_variables
end

method1 中创建了两个局部变量 vw

class Some
    x, y = 2
    puts "Inside class"
    puts local_variables
end

xy 局部变量是在 Some 类的定义中创建的。

t1, t2 = 7

最后,创建了两个属于 Ruby 顶层局部作用域的局部变量。

$ ./locals3.rb
Inside module
m1
m2
Inside class
x
y
Inside method
v
w
Inside toplevel
t1
t2

输出显示了每个局部作用域的局部变量。

Ruby 全局变量

全局变量在脚本的任何地方都有效。 它们在 Ruby 中以 $ 符开头。

不鼓励使用全局变量。 全局变量很容易导致许多编程错误。 仅当有理由这样做时才应使用全局变量。 建议程序员尽可能使用局部变量,而不是全局变量。

globals.rb
#!/usr/bin/ruby

$gb = 6


module ModuleM
    puts "Inside module"
    puts $gb
end


def method1
    puts "Inside method"
    puts $gb
end


class Some
    puts "Inside class"
    puts $gb
end

method1

puts "Inside toplevel"
puts $gb
puts global_variables.include? :$gb

在示例中,我们有一个全局变量 $gb。 我们显示该变量可以在模块、方法、类和顶层中引用。 全局变量 $gb 在所有这些实体中都有效。

$gb = 6

创建了一个全局变量 $gb; 它的值为 6。

module ModuleM
    puts "Inside module"
    puts $gb
end

在模块的定义内部,我们打印全局变量的值。

def method1
    puts "Inside method"
    puts $gb
end

在方法的定义内部,我们打印全局变量的值。

class Some
    puts "Inside class"
    puts $gb
end

在类的定义内部,我们打印全局变量的值。

puts $gb
puts global_variables.include? :$gb

最后,在顶层执行区域中,我们打印全局变量的值,以及该变量是否在由 global_variables 方法生成的数组中。

$ ./globals.rb
Inside module
6
Inside class
6
Inside method
6
Inside toplevel
6
true

该示例的输出确认全局变量在任何地方都是可访问的。

当 Ruby 脚本启动时,它可以访问多个预定义的全局变量。 这些全局变量被认为无害,有助于解决常见的编程作业。

globs.rb
#!/usr/bin/ruby

p $LOAD_PATH
p $:

该脚本显示了一个 $LOAD_PATH 全局变量。 该变量列出了 loadrequire 方法搜索的目录。 $:$LOAD_PATH 名称的短同义词。

本章的预定义变量部分将介绍更多全局变量。

Ruby 实例变量、类变量

在本节中,我们将简要介绍实例和类变量。 它们将在面向对象编程章节中更详细地描述。

实例变量是属于特定对象实例的变量。 每个对象都有自己的对象变量。 实例变量以 @ 符开头。 类变量 属于特定类。 从特定类创建的所有对象共享类变量。 类变量以 @@ 字符开头。

icvars.rb
#!/usr/bin/ruby

class Being

    @@is = true

    def initialize nm
        @name = nm
    end

    def to_s
        "This is #{@name}"
    end

    def does_exist?
        @@is
    end
end

b1 = Being.new "Being 1"
b2 = Being.new "Being 2"
b3 = Being.new "Being 3"

puts b1, b2, b3

p b1.does_exist?
p b2.does_exist?
p b3.does_exist?

我们创建了一个自定义的 Being 类。 Being 类有一个类变量和一个实例变量。

class Being

    @@is = true

@@is 是一个类变量。 该变量由 Being 类的所有实例共享。 此示例的逻辑是 Being 是,而 NotBeing 不是。

def initialize nm
    @name = nm
end

initialize 方法是一个构造函数。 当创建对象时调用该方法。 创建了一个 @name 实例变量。 该变量特定于具体对象。

def to_s
    "This is #{@name}"
end

当对象是打印方法(例如 pputs)的参数时,调用 to_s 方法。 在我们的例子中,该方法给出了对象的简短的、人类可读的描述。

def does_exist?
    @@is
end

does_exist? 方法返回类变量。

b1 = Being.new "Being 1"
b2 = Being.new "Being 2"
b3 = Being.new "Being 3"

Being 类创建了三个对象。 每个对象都有一个不同的名称。 对象的名称将存储在实例方法中,该方法是每个对象实例独有的。 这将在 to_s 方法中使用,该方法给出一个对象的简短描述。

puts b1, b2, b3

puts 方法将创建的对象作为三个参数。 它在每个对象上调用 to_s 方法。

p b1.does_exist?
p b2.does_exist?
p b3.does_exist?

最后,我们调用每个实例的 does_exist? 方法并打印它们的返回值。 这三个方法的输出相同,因为每个方法都返回类变量。

$ ./icvars.rb
This is Being 1
This is Being 2
This is Being 3
true
true
true

该示例的输出。 前三个消息是唯一的。 字符串存储在对象的实例变量中。 true 值是类变量的值,被调用了三次。

Ruby 环境和命令行变量

ENV 常量用于访问环境变量。 它是一个 Ruby 哈希。 每个环境变量都是 ENV 哈希的键。

ARGV 常量保存命令行参数值。 它们由程序员在启动脚本时传递。 ARGV 是一个数组,它将参数存储为字符串。 $*ARGV 的别名。

ENVARGV 都是全局常量。

command_line.rb
#!/usr/bin/ruby

ARGV.each do |a|
  puts "Argument: #{a}"
end

在脚本中,我们循环遍历 ARGV 数组并打印其每个值。

$ ./command_line.rb 1 2 3
Argument: 1
Argument: 2
Argument: 3

我们给出了三个命令行参数。 它们被打印到控制台上,每行一个。

以下示例将处理环境变量。

environment.rb
#!/usr/bin/ruby

puts ENV['SHELL']
puts ENV['LANG']
puts ENV['TERM']

该脚本将向终端打印三个环境变量的值。 这些值取决于我们操作系统的操作系统设置。

$ ./environment.rb 
/bin/bash
en_US.UTF-8
xterm-256color

这是一个示例输出。

Ruby 伪变量

Ruby 有一些变量,称为 伪变量。 它们与常规变量不同。 我们不能为伪变量赋值。

self 是当前方法的接收者。 nilNilClass 的唯一实例。 它表示值的缺失。 trueTrueClass 的唯一实例。 它表示布尔真。 falseFalseClass 的唯一实例。 它表示布尔假。

true 和 false 是布尔数据类型的值。 从另一个角度来看,它们是特定类的实例。 这是因为 Ruby 中的所有内容都是对象。 这看起来不必要地复杂。 但这是前面提到的 Ruby 习惯用法的后果。

pseudo.rb
#!/usr/bin/ruby

p self
p nil
p true
p false

p self.class
p nil.class
p true.class
p false.class

这是伪变量的一个例子。 我们使用 p 方法打印所有四个伪变量。 然后我们找出所有变量的类名。

p self

在这种情况下,self 伪变量返回主执行上下文。

$ ./pseudo.rb
main
nil
true
false
Object
NilClass
TrueClass
FalseClass

示例输出。

在本节的第二个示例中,我们进一步查看 self

pseudo_self.rb
#!/usr/bin/ruby

class Some
    puts self
end

class Other
    puts self
end

puts self

正如我们所说,self 引用当前方法的接收者。 上面的示例显示了三个不同接收者的示例。

class Some
    puts self
end

接收者是名为 Some 的类。

class Other
    puts self
end

这是另一个接收者:名为 Other 的类。

puts self

第三个接收者是 Ruby 顶层。

$ ./pseudo_self.rb
Some
Other
main

本节的最后一个示例将介绍另外三个伪变量。

pseudo2.rb
#!/usr/bin/ruby

if true
    puts "This message is shown"
end

if false
    puts "This message is not shown"
end

p $name
p $age

上面的示例显示了 truefalsenil 伪变量正在工作。

if true
    puts "This message is shown"
end

true 用于布尔表达式。 消息总是被打印出来。

if false
    puts "This message is not shown"
end

此消息永远不会被打印。 条件不满足。 在布尔表达式中,我们总是得到一个负值。

p $name
p $age

如果引用了全局值但未初始化,它们将包含 nil 伪变量。 它代表值的缺失。

$ ./pseudo2.rb
This message is shown
nil
nil

Ruby 预定义的变量

Ruby 有很多预定义的全局变量。 这是 Perl 语言的遗产。 Ruby 受到 Perl 的强烈影响。 它们在 Ruby 脚本启动时是可访问的。 我们有一些预定义的 Ruby 变量的例子。

predefined.rb
#!/usr/bin/ruby

print "Script name: ", $0, "\n"
print "Command line arguments: ", $*, "\n"

puts "Process number of this script: #{$$}"

使用了三个预定义的变量:$0$*$$$0 存储当前脚本的名称。$* 变量存储命令行参数。而$$ 存储脚本的 PID(进程 ID)。

$ ./predefined.rb 1 2 3
Script name: ./predefined.rb
Command line arguments: ["1", "2", "3"]
Process number of this script: 20909

这是一个示例输出。

全局变量$? 存储最后一个已执行子进程的退出状态。

predefined2.rb
#!/usr/bin/ruby

system 'echo "Ruby"'
puts $?

%x[exit '1']
puts $?

我们运行两个外部子进程,并使用$? 变量检查它们的退出状态。

system 'echo "Ruby"'
puts $?

通过使用system方法,我们启动一个子进程。它是一个 echo bash 命令,用于将消息打印到终端。

%x[exit '1']
puts $?

在第二个例子中,我们使用状态 1 执行 bash exit 命令。这次我们使用%x运算符,它在两个选定的分隔符之间执行一个命令。我们选择了 [] 字符。

$ ./predefined2.rb 
Ruby
pid 20952 exit 0
pid 20953 exit 1

第一个子进程以状态 0 终止,第二个子进程以退出状态 1 终止。

在最后一个例子中,我们展示了三个与正则表达式一起使用的全局预定义变量。

predefined3.rb
#!/usr/bin/ruby

"Her name is Jane" =~ /name/

p $`
p $&
p $'

当我们对一个字符串应用=~运算符时,Ruby 会设置一些变量。$& 变量包含与最后一个正则表达式匹配的字符串。$` 包含在 $& 之前的字符串,而 $' 包含在 $& 之后的字符串。

$ ./predefined3.rb
"Her "
"name"
" is Jane"

在本 Ruby 教程中,我们更深入地研究了 Ruby 变量。