1
0
Fork 0
Sebastian Steger 2026-04-07 13:42:49 +00:00
commit 3161053e79
7 changed files with 268 additions and 22 deletions

View File

@ -0,0 +1,26 @@
package main
import "fmt"
type Counter struct {
value int
}
// pointer receiver. The method operates directly on the object c points to
// Use a pointer receiver whenever the object needs to be modified.
func (c *Counter) Increment() {
c.value++
}
// value receiver. The method operates on copy of object c. Use a value
// receiver when the object does not need to be modified in the method.
func (c Counter) String() string {
return fmt.Sprintf("Counter value: %d", c.value)
}
func main() {
c := Counter{}
fmt.Println(c)
c.Increment()
fmt.Println(c)
}

View File

@ -0,0 +1,66 @@
package main
import (
"fmt"
"math"
)
type geometry interface {
area() float64
perim() float64
}
type rect struct {
width, height float64
}
// Ensure that rect implements the geometry interface
var _ geometry = rect{}
type circle struct {
radius float64
}
// Ensure that circle implements the geometry interface
var _ geometry = circle{}
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
//Metods demonstrates how to use an interface
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
//Method demonstrates casting an interface to a concrete type.
//Should be avoided in practice whenever possible.
func detectCircle(g geometry) {
if c, ok := g.(circle); ok {
fmt.Println("circle with radius", c.radius)
}
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
measure(r)
measure(c)
detectCircle(r)
detectCircle(c)
}

View File

@ -0,0 +1,40 @@
package main
import "fmt"
type Person struct {
Name string
}
func (p Person) Say() {
fmt.Println("Hi, my name is", p.Name)
}
type Student struct {
Person //struct Person is embedded
Semester int
}
type Teacher struct {
Person //struct Person is embedded
Subject string
}
func main() {
max := Person{"Max"}
daniel := Student{Person{"Daniel"}, 3}
sebastian := Teacher{Person{"Sebastian"}, "PR3"}
max.Say()
daniel.Say() //can be invoked directly on Student
sebastian.Say() //can be invoked directly on Teacher
max = sebastian.Person
max.Say()
}

View File

@ -0,0 +1,62 @@
package main
import (
"errors"
"fmt"
)
// a function that can fail, so it returns an error as the second return value
func f(arg int) (int, error) {
if arg == 42 {
// in case of an error, return the zero value for the result and a non-nil error
return 0, errors.New("can't work with 42")
}
// simply return the result and a nil error in the success case
return arg + 3, nil
}
// define some errors to use in makeTea
var ErrOutOfTea = fmt.Errorf("no more tea available")
var ErrPower = fmt.Errorf("can't boil water")
// a function that can fail in multiple ways, so it returns an error as the second return value
func makeTea(arg int) error {
if arg == 2 {
// return the error directly, so we can check for it later with errors.Is
return ErrOutOfTea
} else if arg == 4 {
// use %w to wrap the error, so we can check for it later with errors.Is
return fmt.Errorf("making tea: %w", ErrPower)
}
return nil
}
func main() {
for _, i := range []int{7, 42} {
//typical pattern for checking errors in Go: if the error is not nil, handle it and return or continue; otherwise, use the result
if r, e := f(i); e != nil {
fmt.Println("f failed:", e)
} else {
fmt.Println("f worked:", r)
}
}
for i := range 5 {
if err := makeTea(i); err != nil {
// use errors.Is to check for specific errors, even if they are wrapped
if errors.Is(err, ErrOutOfTea) {
fmt.Println("We should buy new tea!")
} else if errors.Is(err, ErrPower) { // this error is wrapped, but errors.Is can still check for it
fmt.Println("Now it is dark.")
} else {
fmt.Printf("unknown error: %s\n", err)
}
continue
}
fmt.Println("Tea is ready!")
}
}

View File

@ -0,0 +1,28 @@
package main
import "fmt"
// a closure is a function value that references variables from outside its body.
// The function may access and assign to the referenced variables; in this sense the function is "bound" to the variables.
func intSeq() func() int { //returns a function that returns an int
i := 0 // i is a variable that intSeq's function value will reference. It continues to exist even after intSeq returns.
return func() int { // the anonomous function is returned here. It is not executed yet.
i++
return i
}
}
func main() {
//functions are first class citizens in Go, so we can assign them to variables,
// pass them as arguments to other functions, and return them from functions.
nextInt := intSeq()
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
//a second function value from intSeq, with its own i variable.
newInts := intSeq()
fmt.Println(newInts())
}

View File

@ -2,7 +2,10 @@ package main
import "fmt" import "fmt"
func SlicesIndex[S []E, E int](s []string, v string) int { // a generic function that takes a slice of any type and a value of that type, and returns the index of the value in the slice, or -1 if it is not found.
// The type parameters S and E are declared in square brackets before the function name, and they can be used in the function signature and body.
// The constraint comparable means that the type E must support the == operator, which is necessary for comparing the value with the elements of the slice.
func SlicesIndex[S []E, E comparable](s S, v E) int {
for i := range s { for i := range s {
if v == s[i] { if v == s[i] {
return i return i
@ -11,27 +14,34 @@ func SlicesIndex[S []E, E int](s []string, v string) int {
return -1 return -1
} }
type List struct { // represents a linked list of any type T. The type parameter T is used in the definition of List and its methods,
head, tail *element // and it can be instantiated with any type when we create a List value. The type any is in fact defined as
// an empty interface. Thanks to duck-typing it can be used to represent any type, but it does not provide any
// operations on the values of that type.
type List[T any] struct {
head, tail *element[T]
} }
type element struct { // the generic element of the linked list
next *element type element[T any] struct {
val int next *element[T]
val T
} }
func (lst *List) Push(v int) { // adds an instance of the generic type to the list
func (lst *List[T]) Push(v T) {
if lst.tail == nil { if lst.tail == nil {
lst.head = &element{val: v} lst.head = &element[T]{val: v}
lst.tail = lst.head lst.tail = lst.head
} else { } else {
lst.tail.next = &element{val: v} lst.tail.next = &element[T]{val: v}
lst.tail = lst.tail.next lst.tail = lst.tail.next
} }
} }
func (lst *List) AllElements() []int { // returns all elements of the list as a slice of the generic type.
var elems []int func (lst List[T]) AllElements() []T {
var elems []T
for e := lst.head; e != nil; e = e.next { for e := lst.head; e != nil; e = e.next {
elems = append(elems, e.val) elems = append(elems, e.val)
} }
@ -39,13 +49,21 @@ func (lst *List) AllElements() []int {
} }
func main() { func main() {
var s = []string{"foo", "bar", "zoo"} var s = []string{"foo", "bar", "zoo"}
// The types S and E are explicitly specified as []string and string respectively.
fmt.Println("index of zoo:", SlicesIndex[[]string, string](s, "zoo"))
fmt.Println("index of zoo:", SlicesIndex(s, "zoo")) var s2 = []int{2, 4, 5, 6}
// The type E is inferred from the type of the value 4, which is int,
// and the type S is inferred from the type of the slice s2, which is []int.
fmt.Println("index of 4: ", SlicesIndex(s2, 4))
lst := List{} // instantiate a list of float64 elements
lst := List[float64]{}
lst.Push(10) lst.Push(10)
lst.Push(13) lst.Push(13)
lst.Push(23) lst.Push(23)
lst.Push(23.3)
fmt.Println("list:", lst.AllElements()) fmt.Println("list:", lst.AllElements())
} }

View File

@ -6,18 +6,22 @@ import (
) )
func main() { func main() {
f := createFile("/tmp/defer.txt")
writeFile(f)
closeFile(f)
}
func createFile(p string) *os.File {
fmt.Println("creating") fmt.Println("creating")
f, err := os.Create(p) f, err := os.Create("/tmp/defer.txt")
// defer the closing of the file until the surrounding function returns.
// This ensures that the file will be closed even if there is a panic or an early return in the function.
defer closeFile(f)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return f
//will not be called in case of a panic before
writeFile(f)
// closeFile will be called here due to the defer-statement, even in case of a panic before.
} }
func writeFile(f *os.File) { func writeFile(f *os.File) {
@ -27,8 +31,10 @@ func writeFile(f *os.File) {
func closeFile(f *os.File) { func closeFile(f *os.File) {
fmt.Println("closing") fmt.Println("closing")
err := f.Close() var err error
if f != nil {
err = f.Close()
}
if err != nil { if err != nil {
panic(err) panic(err)
} }