ZetCode

F# 结构类型

最后修改于 2025 年 5 月 3 日

在本文中,我们将探讨 F# 中的结构类型。结构是值类型,可在特定场景下提供高效的内存使用和性能优势。

F# 中的 结构 是一个值类型,它继承自 System.ValueType,这意味着它的处理方式与类等引用类型不同。与在堆上分配并由垃圾回收器管理的类对象不同,结构通常存储在栈上或嵌入到其他结构中。这种区别使得结构在需要最小化分配开销和提高性能的场景中特别有用。

结构最适合用于小型、频繁使用的数据结构,例如坐标、颜色表示或简单的数学结构。由于结构避免了堆分配,因此有助于减轻内存压力并提高效率——尤其是在性能关键型应用程序中。

在 F# 中,可以使用 [<Struct>] 属性或 struct ... end 语法来定义结构。[<Struct>] 属性应用于标准类型定义,表示该类型应被视为值类型。struct ... end 语法提供了一种更明确的声明结构的方式,确保它们在 F# 类型系统中按预期工作。

但是,与引用类型相比,结构也存在一些限制。它们不支持继承(除了继承自 System.ValueType),这意味着它们不能用在类层次结构中。此外,修改已传递给函数或已分配给变量的结构需要特别注意,因为结构是按值复制而不是按引用复制的。

F# 基本结构类型

最简单的结构类型定义和用法形式。

basic.fsx
[<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# 结构

结构可以像类一样拥有方法和属性。

members.fsx
[<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# 提供了定义结构的替代语法。

syntax.fsx
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# 结构元组

结构元组通过确保值类型语义,提供了常规元组的轻量级替代方案。与作为引用类型并在堆上分配的常规元组不同,结构元组直接存储在内存中,这使得它们对于性能敏感的操作更有效。

tuples.fsx
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# 结构记录

记录可以定义为结构以提高性能。

records.fsx
[<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# 结构限制

结构有一些重要的限制需要考虑。

limitations.fsx
[<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# 结构模式匹配

结构可以像其他类型一样与模式匹配一起使用。

pattern.fsx
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# 中的结构类型、它们的优点、限制以及适当的用例。结构是性能敏感代码的宝贵工具,在这些代码中需要值语义。

作者

我叫 Jan Bodnar,是一位热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。至今,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。