为了正确编写函数并避免不寻常的错误,我们需要了解 R 中的环境和作用域概念。
R 编程环境
环境可以被认为是对象(函数、变量等)的集合。
当我们第一次启动 R 解释器时,就会创建一个环境。我们定义的任何变量现在都存在于这个环境中。
在 R 命令提示符下可用的顶层环境是全局环境,称为 R_GlobalEnv
。
全局环境在 R 代码中也可以称为 .GlobalEnv
。
我们可以使用 ls()
函数来显示当前环境中定义了哪些变量和函数。
此外,我们还可以使用 environment()
函数来获取当前环境。
> a <- 2
> b <- 5
> f <- function(x) x<-0
> ls()
[1] "a" "b" "f"
> environment()
<environment: R_GlobalEnv>
> .GlobalEnv
<environment: R_GlobalEnv>
在上面的示例中,我们可以看到 a
、b
和 f
存在于 R_GlobalEnv
环境中。
请注意,x
(函数参数中的)不在全局环境中。当我们定义一个函数时,会创建一个新环境。
在上面的示例中,函数 f
在全局环境中创建了一个新环境。
实际上,环境有一个帧(frame),其中包含定义的所有对象,还有一个指向其封闭(父)环境的指针。
因此,x
存在于函数 f
创建的新环境的帧中。此环境还将有一个指向 R_GlobalEnv
的指针。
示例:环境的级联
f <- function(f_x){
g <- function(g_x){
print("Inside g")
print(environment())
print(ls())
}
g(5)
print("Inside f")
print(environment())
print(ls())
}
现在,当我们在命令提示符下运行时,我们得到:
> f(6)
[1] "Inside g"
<environment: 0x0000000010c2bdc8>
[1] "g_x"
[1] "Inside f"
<environment: 0x0000000010c2a870>
[1] "f_x" "g"
> environment()
<environment: R_GlobalEnv>
> ls()
[1] "f"
这里,我们在 f
中定义了函数 g
,很明显它们都有不同的环境,并且在其各自的帧中包含不同的对象。
R 编程作用域
让我们看一个例子。
outer_func <- function(){
b <- 20
inner_func <- function(){
c <- 30
}
}
a <- 10
全局变量
全局变量是在程序执行期间始终存在的变量。它可以从程序的任何部分进行更改和访问。
但是,全局变量也取决于函数的视角。
例如,在上面的示例中,从 inner_func()
的角度来看,a
和 b
都是全局变量。
然而,从 outer_func()
的角度来看,b
是局部变量,只有 a
是全局变量。变量 c
对 outer_func()
完全不可见。
局部变量
另一方面,局部变量是在程序特定部分(如函数)内存在的变量,并在函数调用结束时被释放。
在上面的程序中,变量 c
被称为局部变量。
如果我们使用 inner_func()
函数为变量赋值,更改将仅是局部的,并且无法在函数外部访问。
即使全局变量和局部变量的名称匹配,情况也是如此。
例如,如果我们有一个如下的函数:
outer_func <- function(){
a <- 20
inner_func <- function(){
a <- 30
print(a)
}
inner_func()
print(a)
}
当我们调用它时:
> a <- 10
> outer_func()
[1] 30
[1] 20
> print(a)
[1] 10
我们看到变量 a
是在两个函数的环境帧中本地创建的,与全局环境帧不同。
访问全局变量
可以读取全局变量,但当我们尝试为其赋值时,会创建一个新的局部变量。
要对全局变量进行赋值,需要使用超赋值运算符 `<<-`。
当在函数中使用此运算符时,它会在父环境帧中查找变量,如果未找到,它会继续搜索下一级,直到达到全局环境。
如果仍然找不到该变量,则将在全局级别创建并赋值。
outer_func <- function(){
inner_func <- function(){
a <<- 30
print(a)
}
inner_func()
print(a)
}
运行此函数后:
> outer_func()
[1] 30
[1] 30
> print(a)
[1] 30
当在 inner_func()
中遇到语句 a <<- 30
时,它会查找 outer_func()
环境中的变量 a
。
当搜索失败时,它会在 R_GlobalEnv
中进行搜索。
由于 a
在全局环境中也未定义,因此它在此处被创建并赋值,现在它既可以从 inner_func()
内部引用和打印,也可以从 outer_func()
引用和打印。