Initial Commit
This commit is contained in:
173
Code/10.structs1.jl
Normal file
173
Code/10.structs1.jl
Normal file
@@ -0,0 +1,173 @@
|
||||
|
||||
# User defined types are made with the struct keyword. Member variables are
|
||||
# listed in the struct body as follows.
|
||||
# Keep in mind that julia does not support redefining of types by default,
|
||||
# so whenever you change anything in the struct (field names/types, etc.)
|
||||
# you have to restart the REPL.
|
||||
# Alternatively you could look into the package Revise.jl which solves
|
||||
# some of these problems.
|
||||
struct TestType
|
||||
x
|
||||
y
|
||||
z
|
||||
end
|
||||
|
||||
# Every struct gets a default constructor that is the typename taking
|
||||
# in all the member variables in order.
|
||||
t = TestType(1, 2, 3)
|
||||
|
||||
# Every struct also gets a default print function that shows the variable
|
||||
# in style of the constructor.
|
||||
@show t
|
||||
|
||||
# Accessing the member variables of the struct is done simply by just
|
||||
# writing variable_name.field_name
|
||||
# every field is always public as julia does not implement any heavy
|
||||
# object oriented design.
|
||||
@show t.x
|
||||
|
||||
# This line is illegal since struct are immutable by default.
|
||||
# t.y = 5
|
||||
|
||||
# Field mutability can be achieved by adding the keyword "mutable" in front.
|
||||
# However for simple structs, like this xyz-point like structure, it is
|
||||
# much more efficient if you can avoid making it mutable. This has to do
|
||||
# with how mutable struct are allocated differently then immutable ones.
|
||||
# Read more in the "Types" section of the julia documentation.
|
||||
mutable struct TestType2
|
||||
x::Float64
|
||||
y::Float64
|
||||
z::Float64
|
||||
end
|
||||
|
||||
# Also as illustrated above, the fields in a struct can be strictly typed.
|
||||
# This is usually a good idea since it makes the struct take a constant
|
||||
# amount of space, and removes a lot of type bookkeping.
|
||||
|
||||
t2 = TestType2(1.68, 3.14, 2.71)
|
||||
|
||||
# With this mutable struct, the variable now behaves more like a general
|
||||
# container (Array/vector) so you can change the fields.
|
||||
t2.y = 1.23
|
||||
@show t2
|
||||
|
||||
# You can also make parametric types, kind of like templates in C++.
|
||||
# This essentially creates a new type for each new T, so it alleviates the
|
||||
# problem of keeping track of the types of the individual fields.
|
||||
struct TestType3{T}
|
||||
x::T
|
||||
y::T
|
||||
z::T
|
||||
end
|
||||
|
||||
# This now constructs a TestType3{Int64}
|
||||
t3 = TestType3(1, 2, 3)
|
||||
@show t3
|
||||
|
||||
|
||||
# This will be the example struct throughout the rest of the file.
|
||||
struct Polar{T<:Real}
|
||||
r::T
|
||||
θ::T
|
||||
|
||||
# You might want to make your own constructors for a type.
|
||||
# These are typically made inside the struct body since this overrides
|
||||
# the creation of the default constructor described above.
|
||||
function Polar(r::T, θ::T) where T <: Real
|
||||
# Doing some constructy stuff
|
||||
println("Creating Polar number (r = $r, θ = $θ)")
|
||||
|
||||
# The actual variable is created by calling the "new()" function
|
||||
# which acts as the default constructor. This however is just
|
||||
# accessible to functions defined inside the struct body.
|
||||
new{T}(r, θ)
|
||||
end
|
||||
|
||||
# You might want to implement multiple different constructors.
|
||||
# This one is short and simple so it can be created with the inline syntax.
|
||||
# The zero(T) function finds the apropriate "zero" value
|
||||
# for the given type T. That way no implicit conversions have to be done.
|
||||
Polar{T}() where T <: Real = new{T}(zero(T), zero(T))
|
||||
end
|
||||
|
||||
# Not all constructors have to be defined inside the struct body, but here
|
||||
# outside the body we no longer have access to the "new()" function since
|
||||
# the compiler doesn't have any way to infer which type "new" would refer
|
||||
# to out here. So instead we have to use one of the constructors we defined
|
||||
# inside the struct.
|
||||
# This constructor is very similar to the Polar{T}() function but instead
|
||||
# of writing p = Polar{Int}() to take the type in as a template argument
|
||||
# you would pass the type in as an actual argument; p = Polar(Int)
|
||||
Polar(::Type{T}) where T <: Real = Polar{T}()
|
||||
|
||||
|
||||
# Constructing with the first constructor
|
||||
p = Polar(3.14, 2.71)
|
||||
@show p
|
||||
|
||||
# Constructing with the empty constructor
|
||||
p = Polar{Int}()
|
||||
@show p
|
||||
|
||||
# Constructing with the constructor taking the type as an argument
|
||||
p = Polar(Float16)
|
||||
@show p
|
||||
|
||||
|
||||
# You might want to overload som basic operators and functions on your
|
||||
# type. One common thing to want to override is how your variables are printed.
|
||||
# Since there are so many different ways of converting a variable to text
|
||||
# (print, println, @printf, string, @show, display, etc.)
|
||||
# it can be difficult to find out which function is actually responsible
|
||||
# for converting to text.
|
||||
# This function turns out to be the Base.show() function.
|
||||
# It takes in an IO stream object and and the variable you want to show.
|
||||
|
||||
# Since the funtion is from the Base module
|
||||
# we have to override Base.show specifically
|
||||
# Also it is very important to actually specify the type of the variable here
|
||||
# so that we are actually specifying the show function for our type only
|
||||
# and not for any general type.
|
||||
function Base.show(io::IO, p::Polar)
|
||||
# Say we want to print our polar number as r * e^(i θ) style string
|
||||
show(io, p.r) # non strings we want to recursively show
|
||||
write(io, " ⋅ ℯ^(i ⋅ ") # strings we want to write directly with write
|
||||
show(io, p.θ)
|
||||
write(io, ")")
|
||||
end
|
||||
|
||||
# Now our polar numbers are printed as we specified
|
||||
p = Polar(3.14, 2.71)
|
||||
@show p
|
||||
println(p)
|
||||
display(p)
|
||||
|
||||
# Even converting to a string is now done with our show functon
|
||||
s = string(p)
|
||||
@show s
|
||||
|
||||
|
||||
# Overloading operators is even simpler as every infix operator is just a
|
||||
# function; a + b is equivalent to +(a, b) (for all you lisp lovers)
|
||||
|
||||
# For some reason we cannot write the Base.* inline for infix operators
|
||||
import Base.*
|
||||
*(p1::Polar{T}, p2::Polar{T}) where T = Polar(p1.r * p2.r, p1.θ + p2.θ)
|
||||
|
||||
# Multiplication between different types might also be useful
|
||||
*(x::T, p::Polar{T}) where T = Polar(x * p.r, p.θ)
|
||||
# Implementing the reverse mulitplication such that it becomes commutative
|
||||
*(p::Polar{T}, x::T) where T = x * p
|
||||
|
||||
p1 = Polar(2.0, 15.0)
|
||||
p2 = Polar(3.0, 30.0)
|
||||
p3 = p1 * p2
|
||||
@show p3
|
||||
|
||||
# The in-place arithmetic operators (+=, -=, *=, /=, etc.) are not actual
|
||||
# operators but rather just an alias for a = a * b so they need not be
|
||||
# implemented seperately. If the memory copying is a problem and you
|
||||
# really have to do it in-place the way to do that would be to make some
|
||||
# sort of mult!(p, x) function.
|
||||
p3 *= 0.5
|
||||
@show p3
|
||||
Reference in New Issue
Block a user