Rilasciato · in miglioramento
Guida a Go · 3/6
Per ora questo capitolo è disponibile solo in inglese.
Go is statically typed, but type inference keeps it from feeling verbose. This chapter tours the data building blocks of every Go program.
| Kind | Types |
|---|---|
| Boolean | bool |
| Integers | int, int8 to int64, uint, uint8 to uint64 |
| Floating point and complex | float32, float64, complex64, complex128 |
| Strings | string (an immutable sequence of UTF-8 bytes) |
| Aliases | byte (same as uint8), rune (same as int32, a Unicode code point) |
Go never converts between numeric types implicitly. To add an int to a float64 you write float64(n) explicitly. Strings are bytes under the hood, but ranging over a string decodes it into runes, which is what you want for text.
n := 3
f := 1.5
total := float64(n) + f
s := "héllo"
fmt.Println(len(s)) // 6: length in bytes, not characters
for i, r := range s {
fmt.Println(i, string(r)) // the é occupies bytes 1 and 2
}An array has a fixed length that is part of its type, so [3]int and [4]int are different types. In practice you will reach for slices far more often. A slice is a lightweight view onto an underlying array, with a length (len) and a capacity (cap). When append runs out of capacity it allocates a bigger array, which is why you must always assign its result back.
arr := [3]int{1, 2, 3} // array
nums := []int{1, 2, 3} // slice
nums = append(nums, 4, 5)
part := nums[1:3] // [2 3], shares memory with nums
part[0] = 20 // nums[1] is now 20 as well
buf := make([]byte, 0, 64) // length 0, capacity 64
copied := make([]int, len(nums))
copy(copied, nums) // an independent copy
fmt.Println(arr, nums, len(buf), cap(buf), copied)Because sub-slices share storage with the original, use copy or slices.Clone when you need an independent value. The standard slices package also provides everyday helpers such as slices.Sort, slices.Contains, and slices.Index.
A map is a built-in hash table. Reading a missing key returns the zero value of the value type, so when presence matters, use the two-value form and check the second result, conventionally named . Iteration order over a map is deliberately unspecified.
okages := map[string]int{"alice": 31, "bob": 27}
ages["carol"] = 40
if age, ok := ages["dave"]; !ok {
fmt.Println("no dave", age) // age is 0
}
delete(ages, "bob")
for name, age := range ages {
fmt.Println(name, age)
}
counts := make(map[string]int)
counts["go"]++ // missing keys start at zero, so counting just worksA nil map can be read from but panics on write, so create maps with a literal or with make.
A struct groups named fields. Go has no classes; instead you attach methods to types. A method is simply a function with a receiver. Use a pointer receiver (*T) when the method mutates the value or the struct is large, and a value receiver for small read-only types. Within a single type, try to stick to one kind of receiver.
type Point struct {
X, Y float64
}
func (p Point) Dist() float64 { // value receiver: works on a copy
return math.Hypot(p.X, p.Y)
}
func (p *Point) Scale(k float64) { // pointer receiver: mutates the original
p.X *= k
p.Y *= k
}
type Named struct {
Point // embedded: Point's fields and methods are promoted
Name string
}
func main() {
p := Point{X: 3, Y: 4}
p.Scale(2) // Go takes &p for you
n := Named{Point: p, Name: "A"}
fmt.Println(p.Dist(), n.X, n.Dist())
}Instead of inheritance, Go offers embedding: placing one type inside another promotes its fields and methods.
An interface is a set of method signatures. There is no implements keyword: any type that has the right methods satisfies the interface automatically. As a result, interfaces tend to be small and defined by the code that uses them.
type Shape interface {
Area() float64
}
type Rect struct{ W, H float64 }
type Circle struct{ R float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
func describe(v any) string {
switch x := v.(type) { // type switch
case Shape:
return fmt.Sprintf("area %.2f", x.Area())
case string:
return "a string: " + x
default:
return "unknown"
}
}
func main() {
shapes := []Shape{Rect{2, 3}, Circle{1}}
for _, s := range shapes {
fmt.Println(describe(s))
}
}any is an alias for the empty interface interface{} and can hold a value of any type. To get the concrete value back out, use a type assertion v.(T) or a type switch. Go also supports generics through type parameters, so an algorithm that works over many types can be written once, for example func Max[T cmp.Ordered](a, b T) T.
A pointer holds the address of a value: &x takes the address, and *p reads or writes through it. There is no pointer arithmetic, and memory is garbage collected, so returning the address of a local variable is perfectly safe.
func newCounter() *int {
c := 0
return &c // safe: c escapes to the heap
}
func main() {
p := newCounter()
*p++
fmt.Println(*p) // 1
pt := &Point{1, 2}
pt.X = 10 // no need to write (*pt).X
}Go always passes arguments by value. Reach for a pointer when a function must modify its argument or when copying a large struct would be wasteful. Slices and maps already contain references internally, so copying one still points at the same underlying data.
range yields runes.append.v, ok := m[k].
0 commenti
Accedi · Accedi per lasciare un commento.
Scrivi tu il primo commento.