Ruby变量和变量赋值
Ruby变量
关于Ruby变量的一些基本特性
- Ruby中的变量的类型:局部变量、全局变量、实例变量、类变量。当然,还有方法的参数、代码块的参数也认为是变量
- 显然,参数是局部限定在方法内部的,即局部变量
- 但代码块中的参数变量则不一定:如果执行代码块的时候,其内所引用的变量还不存在,则临时创建一个局部于该代码块的变量,如果引用的变量已经存在,则继承变量的作用域属性
- **变量命名惯例 **
- 局部变量、方法参数、方法的名称都使用小写字母开头
- 有时候变量、方法以下划线开头
_name
也是局部的意思,它表示这是私有的东西,不应该暴露给外界
- 有时候变量、方法以下划线开头
- 全局变量以
$
开头,例如$var
- 实例变量以
@
开头,例如@name
- 类变量以
@@
开头,例如@@class_var
- 类名称、模块名称、常量名称都以大写字母开头
- 方法名称可以以
?、!、=
字符结尾,例如equals?
?
字符结尾的方法,表示返回的是一个布尔值,用于测试true/false!
字符结尾的方法,表示警告提醒,这类方法一般表示原处修改(destructive)对象,要小心使用。一般都会提供成对的带有!
结尾和不带!
结尾(non-destructive)的方法供选择。例如uniq()
和uniq!()
,前者修改的是拷贝后的对象,后者在原有对象上修改=
结尾的方法表示赋值行为,例如有一个方法名为test=()
,那么test=(6)
等价于test = 6
。正如数组元素赋值arr[1] = 3
,实际上是调用了[]=
方法,等价于arr[1]=(3)
或arr[1]= 3
。所以,对于面向对象来说,它表现的是setter类方法
- 局部变量、方法参数、方法的名称都使用小写字母开头
- 变量/表达式在字符串中的内插方式是使用
#
开头。在Ruby中,#
前缀可以看作是一种对某对象的引用、调用之义。例如:- 内插全局变量
#$var
- 内插实例变量
#@var
- 内插类变量
#@@var
- 但是对于普通的不带前缀符号的局部变量或表达式,为了避免歧义,通常在
#
后加上{}
- 内插全局变量
Ruby变量的创建和赋值时间点
下面的语句会创建一个局部变量a(前提是之前并不存在这个a变量),它将指向内存中数值对象3(即保存了数值对象3的地址)。
1 | a=3 |
1 | nil? #=> 引用未赋值过的局部变量,将抛出变量未定义错误 a. |
此外,赋值操作并非是执行了赋值语句才赋值,Ruby中的赋值行为是在解析器遇到赋值语句时就赋值的。例如,在一个条件判断语句中做赋值操作,如果条件判断为假,则也会赋值,只不过这时候赋值的是初始值nil:
1 | 3 if false # 条件false,但也创建了变量赋值为nil x= |
变量的赋值语句
简单的,直接一个等号赋值:
1 | a=3 |
还可以多变量同时赋值:
1 | a, b = 1, 2 |
这在Ruby中实际上是隐式创建了数组[1,2],就像Python中逗号分隔的元素将隐式创建一个tuple一样。
还可以:
1 | a, (b, c) = 1, [2, 3] |
不仅如此,Ruby中也能进行数组打包、解包。实际上,上面的多变量同时赋值语句就是一个数组打包、解包的示例。
1 | a=1, 2 # 隐式创建(打包)了一个数组对象 |
更多关于打包、解包相关的内容,参见:splat操作符:参数打包解包。
『丢弃』不要的赋值
可以直接使用一个下划线来『丢弃』值。例如:
1 | a,_=3,4 |
其实并没有真的丢弃,它是将值赋值给了一个特殊的变量名”_”而已。但这是作为一种特殊符号来暗示这个值是不使用的。
splat操作符:参数打包解包
Ruby中可以使用一个星号*
和两个星号**
完成一些打包、解包操作,它们称为splat操作符:
两个星号的splat场景很少见,如有必要可参考上面列出的参考文章。
当splat操作符后面跟的是数组,则进行数组解包操作:解包成元素列表。这个解包效果在调用函数并传参时比较能体现出来。
1 | def f(a,b,c,d) |
上面的*arr
中,splat操作符后面跟的是一个数组,所以它做了解包操作,将数组解包成了4个元素,并依次赋值给参数a、b、c、d。
当splat操作符后面跟的是一个或多个元素,则进行数组打包操作:创建一个新数组保存这些元素。这个在函数定义中比较常见,很偶尔的在赋值的时候也能见到。
1 | def foo(a,b,*args) |
下面的打包、解包示例比较经典:
1 | a,*x=1,2,3 #=> a=1,x=[2,3] |
按引用赋值
Ruby中赋值和函数的参数传递是按引用赋值的。
在Ruby中,变量仅仅只是一个名称,它保存的是对象的地址,可认为变量是一个指针,指向赋值给它的数据对象,或者说是引用那个对象。
例如:
1 | a=3 |
表示创建一个数值对象3,并将这个对象的地址保存在变量a中,通过a能找到这个数值对象3。
于是,将一个变量赋值给另一个变量,实际上是拷贝了地址。
1 | a="hello" #=> "hello" |
所以,如果变量指向的是一个容器类型,那么这样赋值后,修改一个变量会影响另一个变量的值。
1 | a="hello" #=> "hello" |
但因为有些数据类型是不可变类型,即使赋值是按引用赋值使得双方都指向同一个对象,但这时修改一方因为无法修改这个对象,只能创建一个新对象并指向这个新对象,于是两者现在指向不同对象。这就像是”按值传递”而非”按引用传递”的假象。
1 | a=200 # 数值是不可变对象 |
如果不想要这种按引用赋值的效果,那么对于某些类型的对象,可以使用它们特殊的方法来生成内容完全一致的新对象。也可以使用所有对象都通用的dup()或clone()方法直接拷贝对象。
例如,对于字符串,可以直接通过字符串已有的方法返回一个内容一致的字符串新对象。
1 | a="hello" |
使用dup()或clone(),dup()只是简单拷贝对象,clone()是完全拷贝一个对象,包括对象的各种状态。不管如何,dup和clone都能拷贝基本内容完全相同的对象。
1 | a="hello" |