理解byref、ref和&的含义

28

我了解到F#能够管理引用(类似C++的引用)。这使得可以更改在函数中传递的参数的值,并使程序员能够返回多个值。 不过,以下是我需要了解的:

  1. Ref关键字:使用关键字ref,从一个值创建指向该值的推断类型的引用。

let myref = ref 10

这意味着F#将创建一个类型为Ref<int>的对象,并将我的int 10放入其中(可变字段中)。

好的。那么我认为ref用于创建Ref<'a>类型的实例。是吗?

  • 访问值:为了访问存储在引用中的值,可以这样做:

    let myref = ref 10
    let myval = myref.Value
    let myval2 = !myref
    

    尽管 := 运算符能让我像这样修改值:

    let myref = ref 10
    myref.Value <- 30
    myref := 40
    

    所以!(感叹号)使我的引用取消引用。而:=修改它。我想这也是正确的。

  • & 运算符:这个运算符是做什么的?它适用于引用类型吗?不,我猜它必须应用于可变值,然后返回什么?引用?地址?如果使用交互式:

    let mutable mutvar = 10;;
    &a;;
    

    最后一行代码报错,因此我不理解 & 运算符的作用。

  • ByRef: byref 是什么?这对我非常重要,但我意识到我不理解它。 据我了解,它在有关参数传递的函数中使用。当需要修改传递的值时,可以使用 byref(尽管这与函数式语言的哲学有点相悖,但 F# 不仅仅是那样)。请考虑以下示例:

  • let myfunc (x: int byref) =
        x <- x + 10
    
    这很奇怪。我知道如果你有一个引用let myref = ref 10,然后执行以下操作来编辑该值:myref <- 10,它会出现一个错误,因为应该像这样:myref := 10。然而,在那个函数中使用<-操作符来编辑x的事实意味着x不是一个引用,对吗?
    如果我假设x不是引用,那么我也假设在函数中,在参数上使用byref时,该参数可以应用可变语法。所以这只是一种语法问题,如果我这样假设,那么我就没问题了,实际上,一切都正常(没有编译器错误)。但是,x是什么?
    调用函数:如何使用利用byref参数的函数? &操作符涉及其中,但是你能解释得更好一些吗?在这篇文章中:MSDN Parameters and Arguments提供了以下示例:
    type Incrementor(z) =
        member this.Increment(i : int byref) =
           i <- i + z
    
    let incrementor = new Incrementor(1)
    let mutable x = 10
    // A: Not recommended: Does not actually increment the variable. (Me: why?)
    incrementor.Increment(ref x)
    // Prints 10.
    printfn "%d" x  
    
    let mutable y = 10
    incrementor.Increment(&y) (* Me: & what does it return? *)
    // Prints 11.
    printfn "%d" y 
    
    let refInt = ref 10
    incrementor.Increment(refInt) (* Why does it not work in A, but here it does? *)
    // Prints 11.
    printfn "%d" !refInt
    

    8
    风格提示:多个问号并不能使你的问题更具疑问性,反而会让你看起来有点傻:只要一个就足够了。全是大写字母的文本难以阅读,通常被理解为你在喊叫:使用格式化(如斜体和粗体)进行强调。 - R. Martinho Fernandes
    1个回答

    33

    Ref关键字 是的,当你写下 let a = ref 10 时,实际上相当于写下了 let a = new Ref<int>(10),其中 Ref<T> 类型有一个可变字段 Value

    获取值 运算符 :=! 只是写作的快捷方式:

    a.Value <- 10  // same as writing: a := 10
    a.Value        // same as writing: !a
    

    ByRef 是一种特殊类型,只能在方法参数中(合理地)使用。它意味着参数应该是指向某个内存位置的指针(分配在堆或栈上)。它对应于 C# 中的 outref 修饰符。请注意,您不能创建此类型的局部变量。

    & 运算符 是一种创建值(指针)的方式,可以将其作为参数传递给期望 byref 类型的函数/方法。

    通过 byref 调用函数的示例之所以有效,是因为您正在向方法传递对本地可变变量的引用。通过这个引用,该方法可以更改存储在该变量中的值。

    以下代码无法正常工作:

    let a = 10            // Note: You don't even need 'mutable' here
    bar.Increment(ref a)  
    

    原因是你正在创建一个新的 Ref<int> 实例,并将 a 的值复制到此实例中。 然后,Increment 方法修改了存储在 Ref<int> 实例中堆上的值,但您不再具有对此对象的引用。
    let a = ref 10
    bar.Increment(a)  
    

    这是可行的,因为aRef<int>类型的值,您将堆分配实例的指针传递给Increment,然后使用!a从堆分配的引用单元获取该值。
    (您可以将使用ref创建的值作为byref的参数,因为编译器会特殊处理此情况-它将自动引用Value字段,因为这是一个有用的场景...)。

    抱歉,Tomas。但是当传递一个引用到接受按引用传递的函数时,解释器会出现问题... - Andry
    @Andry:你说的“crazy”是指行为出乎意料还是某个错误信息?(我很乐意澄清这一点..) - Tomas Petricek
    对于迟到我很抱歉...它告诉我期望一个ByRef<'a>,但得到了Ref<'a>...这就是困扰我的地方...解释器只是不接受该参数 :( - Andry
    2
    刚看到这个,因为我也遇到了同样的问题。如果在win32 extern API中有一个byref<>,那么需要使用"&"运算符告诉编译器关于byref的信息,否则就像Tomas所说的那样,它应该可以与ref变量一起使用。let mutable pid = 0u GetWindowThreadProcessId(new IntPtr(phwnd),& pid) |> ignore - Fahad

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接