我想理解R在向函数传递参数时使用的逻辑,创建关于内存使用的变量副本等。它什么时候实际创建变量的副本而不是仅仅传递对该变量的引用?特别是我很好奇的情况是:
f <- function(x) {x+1}
a <- 1
f(a)
a
是按字面顺序传递还是引用被传递?
x <- 1
y <- x
副本的参考?什么时候不是这样?
如果有人能向我解释这一点,我将非常感谢。
答案 0 :(得分:14)
当它传递变量时,它总是通过复制而不是通过引用。但是,有时候,在实际发生任务之前,您不会获得副本。该过程的真实描述是传承承诺。看一下文档
?force
?delayedAssign
一个实际意义是,即使不是不可能,也很难避免需要至少两倍于名义上占据的对象的RAM。修改大对象通常需要制作临时副本。
更新:2015:我确实(并且确实)同意Matt Dowle他的data.table包提供了另一种分配路径,可以避免复制 - 重复问题。如果那是要求的更新,那么在提出建议时我并不理解。
最近,apply
和Reduce
的评估规则对R 3.2.1进行了更改。在这里参考了新闻,宣布了这一点:Returning anonymous functions from lapply - what is going wrong?
interesting paper cited by jhetzel in the comments is now here:
答案 1 :(得分:0)
最新答案只是语言设计的一个非常重要的方面,它在网络上(或至少是通常的来源)没有得到足够的报道。
x <- c(0,4,2)
lobstr::obj_addr(x)
# [1] "0x7ff25e82b0f8"
y <- x
lobstr::obj_addr(y)
# [1] "0x7ff25e82b0f8"
请注意相同的“内存地址”,即对象在内存中的存储位置。因此,您可以确认x
和y
都指向相同的标识符。
哈德利·威克汉姆(Hadley Wickham)的Advanced R书对此进行了润饰:
考虑此代码:
x <- c(1, 2, 3)
易于理解为:“创建一个名为“ x”的对象, 包含值1、2和3”。不幸的是 简化将导致关于R的错误预测 实际上是在幕后做的。说的更准确 该代码可做两件事:
它正在创建一个对象,值的向量,
c(1, 2, 3)
。这是 将该对象绑定到名称x
。换句话说,对象,或 值,没有名字;它实际上是具有值的名称。
请注意,它们是临时性的内存地址,并且在每个新的R会话中都会更改。
现在这是重要的部分。
在R语义中,对象是按值复制的。这意味着修改 副本使原始对象保持原样。由于复制数据在 内存是一项昂贵的操作,R中的副本尽可能懒。 它们仅在实际修改了新对象时发生。 来源:[R lang文档] [1]
因此,如果我们现在通过将值附加到向量来修改y
的值,则y
现在指向另一个“对象”。这与文档中有关“仅在修改新对象时”发生的复制操作有关(懒惰的)一致。 y
指向的地址与以前不同。
y <- c(y, -3)
print(lobstr::obj_addr(y))
# [1] "0x7ff25e825b48"
答案 2 :(得分:0)
@onlyphantom超级有用!对象可以在不复制的情况下往返于函数之外:这就是让我来到这里的原因:
tmp <- function(x, create) {
if(!create){
x
}else{
"new"
}
}
x = c(0,4,2)
y = tmp(x, F)
lobstr::obj_addr(x) == lobstr::obj_addr(y) # y points to x!
并且当用其自身“替换” x时,此方法有效-无副本!
oldAddr = lobstr::obj_addr(x)
x = tmp(x, F)
lobstr::obj_addr(x) == oldAddr # TRUE!
手册中的此示例(稍作修改)还有助于触发惰性评估
tmp = function(x, label = deparse(x), force=TRUE) {
if(force){
label
}
x <- x + 1
print(label); return(x)
}
tmp(2)
[1] "2"
[1] 3
tmp(2, force=F)
[1] "3"
[1] 3
R版本3.6.3