ESS多版本 【TA】【ESS】08 变量的作用域

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
8: More variables and scopes 变量的作用域

So far, we’ve been using normal variables such as the one you see in the example below.

到目前为止,我们所使用的变量都是一般变量,我们来看下面的这个例子:

Ruby:
age = 17
if age >= 18
  print "You are 18 or older"
else
  print "You are 17 or younger"
end

Here, age is what’s called a local variable. Any variable that doesn’t start with an uppercase letter or a character like @, @@ or $, is always a local variable.

这里的age就是一个一般变量,这个一般变量实际上是一个局部变量。
局部变量,顾名思义,就只能在局部使用,也就是说,只能在某些特定的地方使用。

In a script, you have multiple scopes. A scope is essentially a range of code with its own collection of methods, local variables, accessibility, et cetera. As we touched upon with methods, a new method has a different scope than the “main” scope. This means that local variables in the main scope aren’t the same as local variables in the method’s scope, even if they have the exact same name.

这些特定的地方,其实就是作用域,之前我们也已经是说过了,不同的作用域并不互通,所以某一个作用域里面的局部变量,你并不能在另一个作用域里使用。
 

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
Constants 常量

While you can’t access local variables from different scopes, constants can help work around that. You can have constants in the main scope, or a class’ scope. What differentiates local variables from constants, is the uppercase letter all constants start with. Variable would be a constant, whereas variable would be a local variable.

虽然局部变量不能在不同的作用域之间被访问,但是常量却可以。
常量就是以大写字母开头的那些变量,也就是说,如果你写age,那么age就是一个局部变量,而如果你写Age,那么Age就是一个常量。

These constants are accessible from anywhere at any time, as seen in the example here.

常量可以在任何地方、任何时候被访问,我们来看下面的这个例子:

Ruby:
Age = 17

def print_age
  print Age
end

print_age #=> 17
# print_age(打印年龄)会打印出Age的值。

This works because the constants can be accessed from different scopes too. The Age constant is defined in the main scope, and is later accessed in the print_age method’s scope. If you were to try this with a local variable, it would raise an error, stating the variable doesn’t exist.

虽然Age是在主作用域被定义的,但是Age依然可以在print_age这个方法的作用域里被访问。

There’s one more difference aside from visibility/accessibility, which comes in when you try to change the value of the constant during run-time. A constant is initialized once, and isn’t meant to be changed after that.In most languages, this isn’t possible in the first place. In Ruby, however, the value does in fact change, but you’ll get a warning that the constant has already been defined before.

另外还有一点是,常量在Ruby中也是可以被改变的,但是,虽然可以,但不推荐你去改变一个常量的值。
 

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
Constants and classes 常量和类

Classes can also have constants within them. When you do, they’re treated kind of similar to class methods and the like. Constants aren’t a part of an instance of the class, but they exist on the class itself. To then reference the constant inside that class, you’ll first have to type the name of the class, followed by ::, followed by the constant name.

类里面可能会有一些常量。
我们可以和调用类方法一样,使用::访问常量,请看下面的这个例子:

Ruby:
class NewClass
  Age = 17
end
print NewClass::Age #=> 17
The Age constant has been created not in the main scope this time, but in NewClass’s scope. This means that you can’t access it directly (which would be print Age), but that you have to go into the class’ scope first (NewClass::).

在这个例子中,即使Age是在NewClass类(新类类)中被定义的,我们依然可以在主作用域中通过NewClass::Age访问它。

You can also have a class in a class in a class, et cetera et cetera. When you then want to refer to a so-called subclass, you also type MainClass::SubClass to reference it. (A subclass is completely independent of the class it’s in; it just has a different scope, which means you always need to type MainClass::SubClass to use it (unless you’re in MainClass itself, in which case just SubClass suffices, because you’re already in the MainClass’s scope)).

在一个类中,可以嵌套另一个类,我们来看下面的例子:

Ruby:
class One
  class Two
    Three = 3
  end
end

print One::Two::Three #=> 3
# 在这个例子中,One里面有个Two,Three(三)在Two中被定义,
# 所以我们可以在主作用域中,通过One::Two::Three访问One里面的Two里面的
# Three。
#
# 看到这里你可能会有点晕,更加准确的说法其实是你通过命名空间(Namespace)进行
# 了数据访问,注意我说的是数据访问,不是方法调用。
#
# 在Ruby中,一个类就是一个命名空间,所以One和Two都是一个命名空间。
# 首先你通过One::Two访问了One命名空间里的Two,接着通过One::Two::Three
# 访问了Two命名空间里的Three。
# 也就是说,如果你要访问命名空间内的实体(类、常量,等等),你就可以使用::,
# 也就是XXX::YYY这样的格式。

(You can’t have a class and a constant with the same name in the same scope.)

注意,在同一个作用域内,你不能有同名的常量和类。
 

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
Instance variables 实例变量

As the name suggests, instance variables are variables bound to class instances and its scope. Whereas local variables exist in one scope only, instance variables are carried by the instance/object of the class. The difference between local variables and instance variables is that the latter always start with 1 ‘@’. Whether or not the letter after that is uppercase or lowercase doesn’t matter.

实例变量,顾名思义,就是专用于实例的变量。
实例变量用来存储实例的某一个属性,或者说状态,我们来看下面的这个例子:

Ruby:
class NewClass
  def initialize
    @age = 10
  end
 
  def print_age
    print @age
  end
end

var = NewClass.new
# var now carries around the @age variable internally.
var.print_age #=> 10

# 在这个例子中,首先通过NewClass.new创建了一个NewClass的实例,
# 因为存在initialize方法,所以会立即调用这个方法,
# 在这个方法中,定义了一个实例变量@age,并且它的值是10,
# 接着,将这个实例和var联系了起来,
# 然后,对var调用print_age(打印年龄)方法,这个方法会打印出@age。
#
# 从这个例子中,我们可以发现,定义实例变量需要在变量名前加一个@。
# 并且,因为实例变量是针对于实例的,所以每一个实例都会有这个实例变量,
# 只要实例存在,实例变量就存在。

If you were to try this with local variables (without the @), it would perform age = 10 in def initialize, but then that method ends and the variable is gone again. Then calling print_age would try to access a non-existent variable, because there’s no local variable defined in print_age called age.

在上面的例子中,如果你不使用实例变量,而是使用局部变量的话,也就是age = 10,那么在初始化方法结束之后,这个变量没有了,你无法在print_age中调用age。
你也可以用作用域来理解,初始化方法的作用域和print_age方法的作用域是独立不互通的,所以它们之间的局部变量不能互相访问,而实例变量就可以在同一个类中的不同方法之间直接访问。

This is solved by using instance variables. These variables exist as long as the object they’re in exists. We can’t directly access this variable from outside of the class though, nor on the object itself (var). You have to be in the class to manipulate instance variables. For this you need to create helper methods, methods that allow you to read and write to the variable from the outside. This is pretty basic:

实例变量虽然可以在同一个类中的不同方法之间直接访问,但是却不能在这个类之外直接进行访问,你只能在这个类之内操纵实例变量。
为了能在外部访问实例变量,我们需要创建一些辅助方法来帮助我们,请看下面的这个例子:

Ruby:
class NewClass
  def initialize
    @age = 10
  end
 
  def get_age
    return @age
  end
 
  def set_age(new_value)
    @age = new_value
  end
end


var = NewClass.new
print var.get_age #=> 10
var.set_age(17)
print var.get_age #=> 17
var.set_age(10)
print var.get_age #=> 10
# 在这个例子中,我们就可以在类的外部通过get_age(得到年龄)来访问@age,
# 通过set_age(设置年龄)重新设置@age的值。
# 也就是说,get_age和set_age就是辅助方法,一个用来访问值,一个用来设置值

But that’s convoluted and annoying to do for every single instance variable (if it’s a big class, you can easily have dozens of instance variables). That’s why we can create getter and setter methods. Getter methods are basically just methods with the same name as the instance variable, which return the value of said instance variable (exactly the same as get_age but with a different name). Setters are a bit different though. Rather than set_age(10), setter methods allow you to use assignment with = to call the method (yes, assignment is also a method!)

但是,为每一个实例变量都添加这样的两个方法是非常烦人的。
所以我们创造了获取器和设置器方法。
获取器是一个和变量的名字相同的方法,而设置器是一个在变量的名字后面加一个“=”的名字的方法。
假如说有个实例变量是@age,那获取器就是age,设置器就是age=,注意,这个“=”是设置器名字的一部分。

First to demonstrate the getter method:

首先我们来看获取器的例子:

Ruby:
class NewClass
  def initialize
    @age = 10
  end

  # Getter
  def age
    return @age
  end
end

p NewClass.new.age #=> 10

Next comes the setter method:

接着我们来看设置器的例子:

Ruby:
class NewClass
  def initialize
    @age = 10
  end

  # Getter
  def age
    return @age
  end

  # Setter
  def age=(new_value)
    @age = new_value
  end
# 通过传递new_value(新的值)来设置@age的值。
end
var = NewClass.new
p var.age #=> 10
var.age = 17
# 注意,这里其实是age=(17),前面其实也说过,参数的括号是可以省略的,
# 所以可以写成age=17,接着你再加一些空格,就变成了age = 17。
# 也就是说其实是对var调用设置器方法,这里的17是传递的参数。
p var.age #=> 17
var.age=(10) # Identical to normal assignment
p var.age #=> 10
# You can even perform mathematical functions with it:
var.age **= 2
# 这里其实是var.age= age * age,也就是说这里其实是结合使用了
# 获取器和设置器。
p var.age #=> 100

So when you create a method with an equal sign = at the end of the method name, that method will be treated as an assignment method, with =.

There’s really no need for getter and setter methods, unless you want the instance variable to BE public and to be used outside of the class itself.

These getter and setter methods can be very useful, but it’s a lot to write if you have a lot of instance variables. That’s why there are shortcuts for these getters and setters:

attr_reader: This is the getter method. Equal to def age in the previous examples.
attr_writer: This is the setter method. Equal to def age=(v) in the previous examples.
attr_accessor: This is both the getter and the setter method. Equal to def age and def age=(v) in the previous examples.

因为获取器和设置器实在是太基础也太重要了,所以我们又创造了更加简便的添加获取器和设置器的方法。
添加获取器,我们只需要写attr_reader(读取器),添加设置器,我们只需要写attr_writer(写入器),而如果你写attr_accessor(访问器)的话,就是同时添加了获取器和设置器。

attr also means Attribute. Attribute reader (getter), Attribute writer (setter), Attribute accessor (getter + setter).

这里的“attr”其实是attribute(属性)的简写,attr_reader就是属性的读取器,attr_writer就是属性的写入器,attr_accessor就是属性的访问器,即读取器+写入器。

To use these shortcuts, you choose the keyword followed by a colon :, followed by the name of the instance variable. When used, it will basically turn into the proper method(s) in the order you wrote them. This means that you can overwrite the functionality later, if you want, just by typing out the method and implementing your desired functionality (just like a normal method).

那么具体该怎么做呢?
我们来看下面的这个例子:

Ruby:
class NewClass
  # They're usually at the top, along with constants
  attr_accessor :age
  # 也就是说,我们通过“attr_accessor :xxx”这样的格式,就可以
  # 快速创建@xxx变量的读取器和设置器。

  def initialize
    @age = 10
  end
end

var = NewClass.new
print var.age #=> 10
var.age = 17
print var.age #=> 17
 
最后编辑:

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
Class variables 类变量

Just like you have instance variables that are linked to instances of the class, class variables exist only on the class itself, and are thus always the same no matter how you access it (different instances of the same class can have different values for their instance variables, this isn’t the case with class variables). All variables that start with @@ are class variables.

还有一种变量叫做类变量。
类变量,顾名思义,就是跟类本身相关联的变量。
也就是说,实例有实例方法和实例变量,类同样有类方法和类变量。
我们来看下面的例子:

Ruby:
class NewClass
  @@my_cvar = 0
end
# 可以看到,我们通过在变量名字前加两个@@来定义一个类变量。

We have no way to access this variable outside of the class currently, so we have to create some more getter and setter methods. Since we’re dealing with class variables that are shared across all instances of the class, we should make them class methods:

那么我们就来新创建一些类方法吧,请看下面的示例:

Ruby:
class NewClass
  @@my_cvar = 0

  def self.my_cvar
    return @@my_cvar
  end

  def self.my_cvar=(v)
    @@my_cvar = v
  end
# 这里我们创建了类变量@@my_cvar(我的类变量,cvar其实就是
# class + var,即类变量)的获取器和设置器。
end

print NewClass.my_cvar #=> 0
NewClass.my_cvar = 1
print NewClass.my_cvar #=> 1

You might not realize why this is a class variable and not an instance variable, but class variables aren’t different per object - each object has the same value of that class variable (technically, they don’t even carry around the variable to begin with; the class itself does that. Instances just look it up in the class), so changing its value means it would change the value everywhere, which can lead to unexpected or unwanted results, such as shown below:

实例变量是跟随实例的,也就是说每一个实例的同一个实例变量的值可能是不同的。
而类变量是跟随类的,对于这个类的所有实例来说,每一个实例的同一个类变量的值都是相同的。
我们来看下面的这个例子:

Ruby:
class NewClass
  @@my_cvar = 0

  def my_cvar
    return @@my_cvar
  end

  def my_cvar=(n)
    @@my_cvar = n
  end
end
obj1 = NewClass.new
obj2 = NewClass.new

p obj1.my_cvar #=> 0
p obj2.my_cvar #=> 0

obj1.my_cvar = 1

p obj1.my_cvar #=> 1
p obj2.my_cvar #=> 1

As useless as class variables might seem (and frankly, you’ll hardly ever use them), they do actually have their uses. What if we wanted to count how many instances we’ve ever created of a class, for instance? We’d have a class variable (independent of any instances) be incremented by 1 every time the constructor is called.

在上面的例子中,我们也可以发现,类变量和实例变量一样,可以在整个类的作用域内被自由访问,不论方法本身是实例方法还是类方法。
我们来看一个使用类变量的例子:

Ruby:
class NewClass
  @@instance_count = 0

  def initialize
    @@instance_count += 1
  end

  def self.instance_count
    return @@instance_count
  end
end

p NewClass.instance_count #=> 0

NewClass.new

p NewClass.instance_count #=> 1

NewClass.new
NewClass.new
NewClass.new

p NewClass.instance_count #=> 4
# 在这个例子中,通过@@instance_count(实例计数)这个类方法,我们
# 可以知道NewClass这个类到底有几个实例,或者说实例化了几次。
 

TAAAAAAA

天王
管理成员
2024/06/16
200
3
27
1,210
Global variables 全局变量

Another type of variables are global variables. As the name implies, these are global can be accessed from everywhere, at any time, in any scope. Unlike constants that are tied to a specific scope, global variables are everywhere. Where instance variables use @ to differentiate them from other variable types, global variables are preceded by a $ sign.

还有一种变量叫做全局变量。
全局变量,顾名思义,就是可以在任何地方、任何时候被访问的变量。
我们在变量的名字前加一个“$”来定义全局变量。
我们来看下面的例子:

Ruby:
$my_gvar = 0

print $my_gvar #=> 0

$my_gvar = 1

print $my_gvar #=> 1

def change_that_var(n)
  $my_gvar = n
end

change_that_var(2)

print $my_gvar #=> 2

class NewClass
  $my_gvar = 3
end

print $my_gvar #=> 3

Global variables are generally more memory-heavy though, so you shouldn’t go overboard with them.

本章完。
 

在线成员

论坛统计

主题
474
消息
2,137
成员
2,909
最新成员
小灵喵~