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 可变对比
| 特性 | struct | mutable 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)
# 现在只能发货或取消
扩展阅读
- Julia 官方文档 — Composite Types
- Julia 官方文档 — Constructors
- Julia 官方文档 — Abstract Types
- Julia 性能优化 — 类型稳定性
- Design Patterns in Julia
📌 本章小结:
struct定义不可变复合类型(性能更优),mutable struct允许字段修改。支持内部/外部构造函数、参数化类型、类型继承。使用show自定义显示,getproperty自定义属性访问。设计时优先使用不可变类型和组合模式。