F# 结构类型
最后修改于 2025 年 5 月 3 日
在本文中,我们将探讨 F# 中的结构类型。结构是值类型,可在特定场景下提供高效的内存使用和性能优势。
F# 中的 结构 是一个值类型,它继承自 System.ValueType,这意味着它的处理方式与类等引用类型不同。与在堆上分配并由垃圾回收器管理的类对象不同,结构通常存储在栈上或嵌入到其他结构中。这种区别使得结构在需要最小化分配开销和提高性能的场景中特别有用。
结构最适合用于小型、频繁使用的数据结构,例如坐标、颜色表示或简单的数学结构。由于结构避免了堆分配,因此有助于减轻内存压力并提高效率——尤其是在性能关键型应用程序中。
在 F# 中,可以使用 [<Struct>] 属性或 struct ... end 语法来定义结构。[<Struct>] 属性应用于标准类型定义,表示该类型应被视为值类型。struct ... end 语法提供了一种更明确的声明结构的方式,确保它们在 F# 类型系统中按预期工作。
但是,与引用类型相比,结构也存在一些限制。它们不支持继承(除了继承自 System.ValueType),这意味着它们不能用在类层次结构中。此外,修改已传递给函数或已分配给变量的结构需要特别注意,因为结构是按值复制而不是按引用复制的。
F# 基本结构类型
最简单的结构类型定义和用法形式。
[<Struct>]
type Point2D =
val X: float
val Y: float
new(x, y) = { X = x; Y = y }
let p = Point2D(1.0, 2.0)
printfn "Point: (%.1f, %.1f)" p.X p.Y
我们定义一个简单的二维点结构并创建一个实例。
[<Struct>] type Point2D =
[<Struct>] 属性将其标记为值类型。
val X: float val Y: float
结构字段使用 val 显式声明。
λ dotnet fsi basic.fsx Point: (1.0, 2.0)
带成员的 F# 结构
结构可以像类一样拥有方法和属性。
[<Struct>]
type Vector2D =
val X: float
val Y: float
new(x, y) = { X = x; Y = y }
member this.Length = sqrt(this.X * this.X + this.Y * this.Y)
member this.Add(v: Vector2D) = Vector2D(this.X + v.X, this.Y + v.Y)
let v1 = Vector2D(3.0, 4.0)
let v2 = Vector2D(1.0, 2.0)
let v3 = v1.Add(v2)
printfn "v1 length: %.2f" v1.Length
printfn "v3: (%.1f, %.1f)" v3.X v3.Y
我们定义一个带有方法和属性的向量结构。
member this.Length = sqrt(this.X * this.X + this.Y * this.Y)
结构可以像类一样拥有计算属性。
λ dotnet fsi members.fsx v1 length: 5.00 v3: (4.0, 6.0)
F# 结构替代语法
F# 提供了定义结构的替代语法。
type Point3D =
struct
val X: float
val Y: float
val Z: float
new(x, y, z) = { X = x; Y = y; Z = z }
end
let p = Point3D(1.0, 2.0, 3.0)
printfn "Point: (%.1f, %.1f, %.1f)" p.X p.Y p.Z
展示了使用替代的 struct...end 语法定义结构。
type Point3D =
struct
...
end
这等同于使用 [<Struct>] 属性。
λ dotnet fsi syntax.fsx Point: (1.0, 2.0, 3.0)
F# 结构元组
结构元组通过确保值类型语义,提供了常规元组的轻量级替代方案。与作为引用类型并在堆上分配的常规元组不同,结构元组直接存储在内存中,这使得它们对于性能敏感的操作更有效。
let regularTuple = (1, "hello", 3.14)
let structTuple = struct (1, "hello", 3.14)
let processStructTuple (t: struct (int, string, float)) =
match t with
| struct (a, b, c) -> printfn "Struct tuple: %d, %s, %f" a b c
let processRegularTuple (t: int * string * float) =
match t with
| (a, b, c) -> printfn "Regular tuple: %d, %s, %f" a b c
processStructTuple structTuple
processRegularTuple regularTuple
在此示例中,创建了一个具有相同值的常规元组和一个结构元组。结构元组使用 struct 关键字显式定义,确保其行为像值类型。由于结构元组和常规元组具有不同的类型表示,我们使用单独的函数来处理它们。函数参数中的显式类型注解可以防止在尝试在同一个匹配表达式中处理两者时出现的模式匹配问题。
λ dotnet fsi tuples.fsx Struct tuple: 1, hello, 3.140000 Regular tuple: 1, hello, 3.140000
F# 结构记录
记录可以定义为结构以提高性能。
[<Struct>]
type Person =
{ Name: string
Age: int }
let p1 = { Name = "John"; Age = 30 }
let p2 = { p1 with Name = "Jane" }
printfn "Person: %s, %d" p1.Name p1.Age
printfn "Person: %s, %d" p2.Name p2.Age
展示了如何定义和使用结构记录。
[<Struct>] type Person =
[<Struct>] 属性使此记录成为值类型。
λ dotnet fsi records.fsx Person: John, 30 Person: Jane, 30
F# 结构限制
结构有一些重要的限制需要考虑。
[<Struct>]
type LimitedStruct =
val X: int
val Y: string // Structs can have reference type fields
new(x, y) = { X = x; Y = y }
// Structs cannot use let bindings for fields
// [<Struct>]
// type InvalidStruct =
// let x = 5 // Compilation error
let s = LimitedStruct(1, "test")
printfn "Struct: %d, %s" s.X s.Y
演示了 F# 中结构类型的一些限制。
val Y: string // Structs can have reference type fields
结构可以包含引用类型,但会失去一些优势。
λ dotnet fsi limitations.fsx Struct: 1, test
F# 结构模式匹配
结构可以像其他类型一样与模式匹配一起使用。
open System
[<Struct>]
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
let area shape =
match shape with
| Circle r -> Math.PI * r * r
| Rectangle (w, h) -> w * h
let circle = Circle(5.0)
let rect = Rectangle(4.0, 6.0)
printfn "Circle area: %.2f" (area circle)
printfn "Rectangle area: %.2f" (area rect)
展示了如何定义结构歧义联合以及针对它们进行模式匹配。
| Circle of radius: float
结构歧义联合的定义方式与常规歧义联合相同。
λ dotnet fsi pattern.fsx Circle area: 78.54 Rectangle area: 24.00
在本文中,我们探讨了 F# 中的结构类型、它们的优点、限制以及适当的用例。结构是性能敏感代码的宝贵工具,在这些代码中需要值语义。