强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Julia 教程 / 复合类型(struct)

复合类型(struct)

1. 不可变 struct

# 基本定义
struct Point
    x::Float64
    y::Float64
end

# 创建实例
p = Point(1.0, 2.0)

# 访问字段
p.x        # 1.0
p.y        # 2.0

# 不可变!不能修改字段
# p.x = 3.0    # ERROR: immutable struct

# 获取字段名
fieldnames(Point)    # (:x, :y)

# 获取字段数
fieldcount(Point)    # 2

💡 提示: 不可变类型的数据存储在栈上(小对象时),没有垃圾回收开销,性能更好。只要不需要修改字段,优先使用不可变 struct


2. 可变 mutable struct

mutable struct BankAccount
    owner::String
    balance::Float64
    active::Bool
end

# 创建可变实例
account = BankAccount("Alice", 1000.0, true)

# 可以修改字段
account.balance -= 200.0    # 800.0
account.active = false      # false

println(account)
# BankAccount("Alice", 800.0, false)

不可变 vs 可变对比

特性structmutable struct
字段修改❌ 不允许✅ 允许
内存分配栈(小对象)/ 堆
垃圾回收可能不需要需要
性能⭐⭐⭐⭐⭐⭐⭐⭐
可做 Dict 的 key
可用 === 比较按值按引用

3. 构造函数

默认构造函数

struct Person
    name::String
    age::Int
end

# Julia 自动生成默认构造函数
p = Person("Alice", 30)

# 类型检查自动执行
# Person("Alice", -5)    # 不会报错(Julia 不强制业务逻辑)

自定义内部构造函数

struct PositivePoint
    x::Float64
    y::Float64
    
    # 内部构造函数(使用 new 关键字)
    function PositivePoint(x, y)
        if x < 0 || y < 0
            error("坐标必须为非负数")
        end
        return new(x, y)
    end
end

PositivePoint(1.0, 2.0)     # OK
# PositivePoint(-1.0, 2.0)  # ERROR: 坐标必须为非负数

自定义外部构造函数

struct RGB
    r::UInt8
    g::UInt8
    b::UInt8
end

# 外部构造函数(便捷方法)
RGB(hex::String) = RGB(
    parse(UInt8, hex[1:2], base=16),
    parse(UInt8, hex[3:4], base=16),
    parse(UInt8, hex[5:6], base=16)
)

# 默认值
RGB() = RGB(0, 0, 0)    # 黑色

# 使用
color1 = RGB(255, 0, 0)      # 红色
color2 = RGB("#00FF00")       # 绿色(使用十六进制构造)
color3 = RGB()                # 黑色

4. 参数化类型

# 类型参数使得类型更通用
struct Vec2{T}
    x::T
    y::T
end

# 不同的类型参数产生不同的具体类型
v1 = Vec2(1, 2)           # Vec2{Int64}
v2 = Vec2(1.0, 2.0)       # Vec2{Float64}
v3 = Vec2(1.0f0, 2.0f0)   # Vec2{Float32}
v4 = Vec2{Int}(3, 4)      # 显式指定类型参数

typeof(v1)                 # Vec2{Int64}
typeof(v2)                 # Vec2{Float64}

# 不允许混合类型
# Vec2(1, 2.0)             # ERROR: 两个参数类型必须一致

多个类型参数

struct Pair{A, B}
    first::A
    second::B
end

p = Pair("name", 42)               # Pair{String, Int64}
p = Pair{String, Int}("name", 42)  # 显式指定

# 带约束的类型参数
struct SortedPair{T<:Real}
    lo::T
    hi::T
    
    function SortedPair{T}(a, b) where T
        a <= b ? new(a, b) : new(b, a)
    end
end

SortedPair{Float64}(3.0, 1.0)    # SortedPair{Float64}(1.0, 3.0)

5. 类型别名

# Julia 1.6+ 支持 const 类型别名
const Point2D = Vec2{Float64}
const Point3D = Vec3{Float64}

# 使用
p = Point2D(1.0, 2.0)    # 等价于 Vec2{Float64}(1.0, 2.0)

# 泛型类型别名
const Matrix2x2{T} = Matrix{T}    # 不是直接支持的语法
# 实际使用 parametric type alias 需要包如 TypeTransformations.jl

6. 空构造函数与默认值技巧

# 模拟默认值(Julia struct 不直接支持字段默认值)
mutable struct Config
    host::String
    port::Int
    debug::Bool
    max_connections::Int
end

# 使用外部构造函数实现默认值
function Config(;
    host="localhost",
    port=8080,
    debug=false,
    max_connections=100
)
    return Config(host, port, debug, max_connections)
end

# 使用
cfg = Config()                          # 全部默认值
cfg = Config(port=3000, debug=true)     # 部分自定义

# 也可用 nothing 表示可选字段
struct OptionalConfig
    host::Union{String, Nothing}
    port::Union{Int, Nothing}
end

7. struct 与内存布局

# 查看类型大小
sizeof(Point)           # 16(两个 Float64 各 8 字节)

# 数组中的内存布局
points = [Point(i, i*2) for i in 1:1000]
# 这是 Array{Point, 1},数据在内存中连续存储
# 比 Array{Any} 性能好得多

# 对比:引用数组
refs = Any[Point(i, i*2) for i in 1:1000]
# Array{Any, 1},每个元素是指针,不连续

# 内存对齐
struct Aligned
    a::UInt8     # 1 字节
    b::UInt32    # 4 字节(可能有 padding)
    c::UInt8     # 1 字节
end
sizeof(Aligned)    # 可能 > 6(编译器可能添加填充)

⚠️ 注意: 不可变类型的字段顺序影响内存布局。将大字段放在前面,小字段放在后面,有助于减少内存对齐填充。


8. propertynames 与自定义访问

struct Temperature
    kelvin::Float64
end

# 自定义属性访问
function Base.getproperty(t::Temperature, s::Symbol)
    if s === :kelvin
        return getfield(t, :kelvin)
    elseif s === :celsius
        return getfield(t, :kelvin) - 273.15
    elseif s === :fahrenheit
        return (getfield(t, :kelvin) - 273.15) * 9/5 + 32
    else
        throw(ArgumentError("未知属性: $s"))
    end
end

# 自定义属性列表
function Base.propertynames(t::Temperature)
    return (:kelvin, :celsius, :fahrenheit)
end

# 使用
t = Temperature(373.15)
t.kelvin       # 373.15
t.celsius      # 100.0
t.fahrenheit   # 212.0

9. 自定义显示(show)

struct Color
    r::UInt8
    g::UInt8
    b::UInt8
end

# 自定义 REPL 显示
function Base.show(io::IO, c::Color)
    print(io, "Color(#$(string(c.r, base=16, pad=2))" *
              "$(string(c.g, base=16, pad=2))" *
              "$(string(c.b, base=16, pad=2)))")
end

# 自定义多行显示
function Base.show(io::IO, ::MIME"text/plain", c::Color)
    println(io, "Color:")
    println(io, "  R: $(c.r) ($(round(c.r/255, digits=2)))")
    println(io, "  G: $(c.g) ($(round(c.g/255, digits=2)))")
    print(io,   "  B: $(c.b) ($(round(c.b/255, digits=2)))")
end

# 使用
c = Color(255, 128, 0)
c                       # Color(#ff8000)
show(stdout, c)         # Color(#ff8000)
show(stdout, MIME"text/plain"(), c)
# Color:
#   R: 255 (1.0)
#   G: 128 (0.5)
#   B: 0 (0.0)

10. 继承(abstract type)

# 定义抽象类型
abstract type Animal end

# 具体子类型
struct Dog <: Animal
    name::String
    breed::String
end

struct Cat <: Animal
    name::String
    indoor::Bool
end

struct Bird <: Animal
    name::String
    can_fly::Bool
end

# 类型层次
supertype(Dog)          # Animal
supertype(Animal)       # Any

subtypes(Animal)        # [Bird, Cat, Dog]

# 多重派发
function speak(a::Dog)
    println("$(a.name) says: 汪汪!")
end

function speak(a::Cat)
    println("$(a.name) says: 喵喵~")
end

function speak(a::Bird)
    println("$(a.name) says: 叽叽!")
end

# 使用
animals = [Dog("旺财", "金毛"), Cat("咪咪", true), Bird("小翠", true)]
for a in animals
    speak(a)
end

抽象类型不能实例化

# Animal("test")    # ERROR: 不能创建抽象类型的实例

# 类型检查
isa(Dog("旺财", "金毛"), Animal)    # true
isa(42, Animal)                     # false

# 参数化抽象类型
abstract type Container{T} end

struct Box{T} <: Container{T}
    item::T
end

b = Box(42)               # Box{Int64}
isa(b, Container{Int})     # true
isa(b, Container)          # true

11. 类型层次设计模式

接口模式(类似 trait)

abstract type Drawable end

struct Circle <: Drawable
    radius::Float64
end

struct Rectangle <: Drawable
    width::Float64
    height::Float64
end

# 接口函数
area(d::Drawable) = error("area not implemented for $(typeof(d))")
perimeter(d::Drawable) = error("perimeter not implemented for $(typeof(d))")

# 实现接口
area(c::Circle) = π * c.radius^2
area(r::Rectangle) = r.width * r.height

perimeter(c::Circle) = 2π * c.radius
perimeter(r::Rectangle) = 2 * (r.width + r.height)

# 使用
shapes = [Circle(5.0), Rectangle(4.0, 6.0)]
for s in shapes
    println("$(typeof(s)): 面积=$(round(area(s), digits=2)), 周长=$(round(perimeter(s), digits=2))")
end

组合优于继承

# 不推荐:深层继承
abstract type Vehicle end
abstract type Car <: Vehicle end
abstract type ElectricCar <: Car end

# 推荐:组合(Composition over Inheritance)
struct Engine
    type::String    # "gasoline", "diesel", "electric"
    horsepower::Float64
end

struct Car
    make::String
    model::String
    year::Int
    engine::Engine
end

# 通过组合实现灵活性
electric_engine = Engine("electric", 300.0)
my_tesla = Car("Tesla", "Model 3", 2024, electric_engine)

12. 业务场景:订单系统

abstract type OrderStatus end
struct Pending <: OrderStatus end
struct Shipped <: OrderStatus end
struct Delivered <: OrderStatus end
struct Cancelled <: OrderStatus end

struct OrderItem
    product::String
    quantity::Int
    price::Float64
end

struct Order{S<:OrderStatus}
    id::String
    items::Vector{OrderItem}
    status::S
    created_at::String
end

# 状态转换函数(多重派发)
ship_order(o::Order{Pending}) = Order(o.id, o.items, Shipped(), o.created_at)
deliver_order(o::Order{Shipped}) = Order(o.id, o.items, Delivered(), o.created_at)
cancel_order(o::Order{Pending}) = Order(o.id, o.items, Cancelled(), o.created_at)

# 不合法的状态转换会报错(没有匹配的方法)
# ship_order(delivered_order)    # MethodError

# 计算订单总额
function total(order::Order)
    return sum(item.price * item.quantity for item in order.items)
end

# 使用
items = [OrderItem("iPhone", 1, 999.0), OrderItem("Case", 2, 29.99)]
order = Order("ORD-001", items, Pending(), "2024-01-15")
total(order)    # 1058.98

shipped = ship_order(order)
# 现在只能发货或取消

扩展阅读


📌 本章小结: struct 定义不可变复合类型(性能更优),mutable struct 允许字段修改。支持内部/外部构造函数、参数化类型、类型继承。使用 show 自定义显示,getproperty 自定义属性访问。设计时优先使用不可变类型和组合模式。