參照儲存格 (F#)
「參考儲存格」(Reference Cell) 是可讓您以參考語意建立可變值的儲存位置。
ref expression
備註
您可以在值的前面使用 ref 運算子,建立可封裝值的新參考儲存格。由於基礎值是可變的,因此您接著可以變更這個基礎值。
參考儲存格不只是地址,還會保存實際值。當您使用 ref 運算子建立參考儲存格時,會建立基礎值的複本做為已封裝的可變值。
您可以使用 ! (驚嘆號) 運算子來取值 (Dereference) 參考儲存格。
下列程式碼範例將示範參考儲存格的宣告和用法。
// Declare a reference.
let refVar = ref 6
// Change the value referred to by the reference.
refVar := 50
// Dereference by using the ! operator.
printfn "%d" !refVar
輸出為 50。
參考儲存格為 Ref 泛型記錄型別的執行個體,其宣告如下。
type Ref<'a> =
{ mutable contents: 'a }
'a ref 型別是 Ref<'a> 的同義字。IDE 中的編譯器和 IntelliSense 會對此型別顯示前者,但基礎定義則為後者。
ref 運算子會建立新的參考儲存格。下列程式碼為 ref 運算子的宣告。
let ref x = { contents = x }
下表顯示參考儲存格上的可用功能。
運算子、成員或欄位 |
描述 |
型別 |
定義 |
---|---|---|---|
! (取值運算子) |
傳回基礎值。 |
'a ref -> 'a |
let (!) r = r.contents |
:= (指派運算子) |
變更基礎值。 |
'a ref -> 'a -> unit |
let (:=) r x = r.contents <- x |
ref (運算子) |
將值封裝至新的參考儲存格。 |
'a -> 'a ref |
let ref x = { contents = x } |
Value (屬性) |
取得或設定基礎值。 |
unit -> 'a |
member x.Value = x.contents |
contents (記錄欄位) |
取得或設定基礎值。 |
'a |
let ref x = { contents = x } |
有數個方式可以存取基礎值。取值運算子 (!) 傳回的值不是可指派的值。因此如果您要修改基礎值,則必須改用指派運算子 (:=)。
Value 屬性和 contents 欄位都是可指派的值。因此,您可以使用它們來存取或變更基礎值,如下列程式碼所示。
let xRef : int ref = ref 10
printfn "%d" (xRef.Value)
printfn "%d" (xRef.contents)
xRef.Value <- 11
printfn "%d" (xRef.Value)
xRef.contents <- 12
printfn "%d" (xRef.contents)
輸出如下。
10
10
11
12
contents 欄位是針對與其他 ML 版本相容而提供,而且會在編譯期間產生警告。若要停用這個警告,請使用 --mlcompatibility 編譯器選項。如需詳細資訊,請參閱編譯器選項 (F#)。
範例
下列程式碼將示範如何將參考儲存格用於參數傳遞。Incrementor 型別的方法 Increment 會接受參數型別中包含 byref 的參數。參數型別中的 byref 表示呼叫端必須傳遞參考儲存格或指定之型別的一般變數位址,該型別在此情況下為 int。其餘的程式碼示範如何搭配這兩種引數來呼叫 Increment,以及示範如何在變數上使用 ref 運算子來建立參考儲存格 (ref myDelta1)。接著,程式碼會示範如何使用傳址運算子 (&) 來產生適當引數。最後,再使用透過 let 繫結所宣告的參考儲存格,再次呼叫 Increment 方法。最後一行程式碼示範如何使用 ! 運算子來取值參考儲存格供列印之用。
type Incrementor(delta) =
member this.Increment(i : int byref) =
i <- i + delta
let incrementor = new Incrementor(1)
let mutable myDelta1 = 10
incrementor.Increment(ref myDelta1)
// Prints 10:
printfn "%d" myDelta1
let mutable myDelta2 = 10
incrementor.Increment(&myDelta2)
// Prints 11:
printfn "%d" myDelta2
let refInt = ref 10
incrementor.Increment(refInt)
// Prints 11:
printfn "%d" !refInt
如需如何以傳址方式傳遞的詳細資訊,請參閱參數和引數 (F#)。
注意事項 |
---|
C# 程式設計人員應該知道 F# 中 ref 的運作方式不同於 C#。例如,傳遞引數時,F# 與 C# 中的 ref 有不同的效果。 |
參考儲存格與可變變數
參考儲存格和可變變數通常都可用於相同的情況。不過,有些情況下可變變數無法使用,這時必須改用參考儲存格。一般而言,在編譯器接受可變變數的情況下,使用可變變數是較常見的作法。不過,在產生封閉區段的運算式中,編譯器就會報告無法使用可變變數。「封閉區段」(Closure) 是特定 F# 運算式所產生的區域函式,例如 Lambda 運算式、序列運算式、計算運算式,以及使用部分已套用之引數的局部調用函式。這些運算式所產生的封閉區段會儲存供稍後評估之用。這個程序與可變變數不相容。因此,如果在這類運算式中需要可變狀態,則必須使用參考儲存格。如需封閉區段的詳細資訊,請參閱<封閉區段 (F#)>。
下列程式碼範例將示範必須使用參考儲存格的案例。
// Print all the lines read in from the console.
let PrintLines1() =
let mutable finished = false
while not finished do
match System.Console.ReadLine() with
| null -> finished <- true
| s -> printfn "line is: %s" s
// Attempt to wrap the printing loop into a
// sequence expression to delay the computation.
let PrintLines2() =
seq {
let mutable finished = false
// Compiler error:
while not finished do
match System.Console.ReadLine() with
| null -> finished <- true
| s -> yield s
}
// You must use a reference cell instead.
let PrintLines3() =
seq {
let finished = ref false
while not !finished do
match System.Console.ReadLine() with
| null -> finished := true
| s -> yield s
}
在前述的程式碼中,參考儲存格 finished 包含於區域狀態中,也就是位於封閉區段中的變數只在運算式 (在此情況下為序列運算式) 內建立及使用。試想當變數為非區域變數時會發生什麼狀況。封閉區段也可以存取非區域性狀態,但發生這種狀況時,變數會以傳值方式複製及儲存。這個程序稱為「實值語意」(Value Semantics)。這表示會儲存複製時的值,而且不會反映變數的任何後續變更。如果您要追蹤非區域變數的變更,換句話說,需要透過「參考語意」(Reference Semantics) 與非區域狀態互動的封閉區段時,則必須使用參考儲存格。
在下列程式碼範例中,將示範參考儲存格在封閉區段內的用法。在此情況下,封閉區段會因套用部分函式引數而有所不同。
// The following code demonstrates the use of reference
// cells to enable partially applied arguments to be changed
// by later code.
let increment1 delta number = number + delta
let mutable myMutableIncrement = 10
// Closures created by partial application and literals.
let incrementBy1 = increment1 1
let incrementBy2 = increment1 2
// Partial application of one argument from a mutable variable.
let incrementMutable = increment1 myMutableIncrement
myMutableIncrement <- 12
// This line prints 110.
printfn "%d" (incrementMutable 100)
let myRefIncrement = ref 10
// Partial application of one argument, dereferenced
// from a reference cell.
let incrementRef = increment1 !myRefIncrement
myRefIncrement := 12
// This line also prints 110.
printfn "%d" (incrementRef 100)
// Reset the value of the reference cell.
myRefIncrement := 10
// New increment function takes a reference cell.
let increment2 delta number = number + !delta
// Partial application of one argument, passing a reference cell
// without dereferencing first.
let incrementRef2 = increment2 myRefIncrement
myRefIncrement := 12
// This line prints 112.
printfn "%d" (incrementRef2 100)