Go language basic accumulation
Based on the content
Basic data types
- bool
- string
- int int8 int16 int32 int64
- uint uint8 uint16 uint32 uint64 uintptr
- Byte // Uint8 alias
- Rune // Int32’s alias // represents a Unicode code point
- float32 float64
- complex64 complex128
This example shows several types of variables. Like import statements, variable declarations can be “grouped” into a syntax block.
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(- 5 + 12i))// %T Value type %v value
func main(a) {
fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
fmt.Printf("Type: %T Value: %v\n", z, z)
}
Copy the code
Int, uint and uintptr are typically 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems. Use int when you need an integer value, unless you have a special reason to use fixed-size or unsigned integers.
Zero value
Variable declarations that do not have an explicit initial value are given their zero value. Zero value is:
- The value type is
0
- The Boolean type is
false
- The string for
""
(Empty string)
func main(a) {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
Copy the code
Type conversion
The expression T(v) converts the value v to type T.
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
Copy the code
Or, in simpler form:
i := 42
f := float64(i)
u := uint(f)
Copy the code
Unlike C, Go requires explicit conversions when assigning values between items of different types.
Type inference
When declaring a variable without specifying its type (that is, using untyped := syntax or var = expression syntax), the type of the variable is derived from the rvalue. When an rvalue declares a type, the new variable has the same type:
var i int
j := i // j is also an int
Copy the code
However, if the right-hand side contains an unspecified numeric constant, the new variable may be of type int, float64, or complex128, depending on the precision of the constant:
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5 I // complex128
Copy the code
constant
Constants are declared like variables, but with the const keyword. Constants can be characters, strings, booleans, or numeric values. Constants cannot be declared with the := syntax.
const Pi = 3.14
const World = "The world"
const Truth = true
Copy the code
Numerical constants
Numerical constants are values of high precision. An untyped constant is typed by context. Try needInt(Big) again. (The int type can store up to one 64-bit integer, sometimes smaller.) (Int can hold up to 64-bit integers, sometimes less depending on the platform.)
const (
// Move 1 100 places to the left to create a very large number
// The binary number is 1 followed by 100 zeros
Big = 1 << 100
// Move 99 bits to the right, Small = 1 << 1, or Small = 2
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main(a) {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
Copy the code
Process and control statements
For
Go has only one loop structure: the for loop. The basic for loop consists of three parts separated by a semicolon:
- Initialization statement: Execute before the first iteration
- Conditional expression: Evaluated before each iteration
- Post-statement: Executes at the end of each iteration
The initialization statement is typically a short variable declaration that is visible only in the scope of the for statement. Once the Boolean value of the conditional expression is false, the loop iteration is terminated. Note: Unlike C, Java, JavaScript, etc., Go does not have curly braces around the three components of the for statement. Curly braces {} are required.
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
Copy the code
- Initialization statements and post-statements are optional
sum := 1
for ; sum < 1000; {
sum += sum
}
Copy the code
While
For is the “while” in Go and you can drop the semicolon at this point, because C’s while is called for in Go
sum := 1
for sum < 1000 {
sum += sum
}
Copy the code
An infinite loop
If the loop condition is omitted, the loop does not end, so an infinite loop can be written very compact.
for {
// xxx
}
Copy the code
If
The if statement of Go is similar to the for loop in that curly braces () are not required, whereas curly braces {} are required.
if x < 0 {
return sqrt(-x) + "i"
}
Copy the code
Like for, an if statement can execute a simple statement before a conditional expression. This statement declares variables scoped only within if.
if v := math.Pow(x, n); v < lim {
return v
}
Copy the code
Variables declared in the short if statement can also be used in any corresponding else block.
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
Copy the code
Switch
Switch is a convenient way to write a series of if-else statements. It runs the first case statement whose value is equal to the conditional expression.
package main
import (
"fmt"
"runtime"
)
func main(a) {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
}
Copy the code
Go’s switch statements are similar to those in C, C++, Java, JavaScript, and PHP, except that Go only runs selected cases, not all subsequent cases. In fact, Go automatically provides the break statement required after every case in these languages. The branch terminates automatically unless terminated with a FallThrough statement. Another important difference with Go is that the case of the switch does not have to be constant and the value does not have to be an integer.
No conditional switch
Unconditional switch is the same as switch true. This form makes a long list of if-then-else’s much clearer.
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")}Copy the code
Defer
The defer statement will defer execution of the function until the outer function returns. A delayed function is evaluated immediately, but is not called until the outer function returns.
func main(a) {
defer fmt.Println("world")
fmt.Println("hello")}// hello
// world
Copy the code
Defer the stack
Delayed function calls are pushed onto a stack. When the outer function returns, the delayed function is called in last-in, first-out order.
func main(a) {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")}// counting
// done
/ / 9
/ / 8
/ / 7
/ / 6
/ / 5
/ / 4
/ / 3
/ / 2
/ / 1
/ / 0
Copy the code
More types
Pointer to the
Go has Pointers. Pointers hold the memory address of the value. Type *T is a pointer to a value of type T. Its zero value is nil.
var p *int
Copy the code
The & operator generates a pointer to its operand.
i := 42
p = &i
Copy the code
The * operator represents the underlying value to which the pointer points.
fmt.Println(*p) // Read I through the pointer p
*p = 21 // Set I by pointer p
Copy the code
This is often referred to as “indirect reference” or “redirection”. Unlike C, Go has no pointer operation.
Structure Struct
A struct is a group of fields.
type Vertex struct {
X int
Y int
}
func main(a) {
fmt.Println(Vertex{1.2})}Copy the code
Structure field
Structure fields are accessed using periods.
v := Vertex{1.2}
v.X = 4
fmt.Println(v.X)
/ / 4
Copy the code
Structure pointer
Structure fields can be accessed through structure Pointers. If we have a pointer p to a structure, we can access its field X by (*p).x. But this is too verbose, so the language also allows us to use implicit indirect references, just p.X.
v := Vertex{1.2}
p := &v
p.X = 1e9
fmt.Println(v)
/ / {1000000000}
Copy the code
Structural grammar
The structure grammar allocates a new structure by listing the values of the fields directly. Use the Name: syntax to list only partial fields. (The order of field names is irrelevant.) The special prefix & returns a pointer to the structure.
var (
v1 = Vertex{1.2} // Create a structure of type Vertex
v2 = Vertex{X: 1} // Y:0 is implicitly assigned
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1.2} // Create a structure of type *Vertex.
)
Copy the code
An Array of Array
The type [n]T represents an array with n values of type T. expression
var a [10]int
Copy the code
The variable a is declared as an array of 10 integers. The length of an array is part of its type, so arrays cannot be resized. This may seem like a limitation, but no matter, Go provides a more convenient way to use arrays.
func main(a) {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2.3.5.7.11.13}
fmt.Println(primes)
}
Copy the code
slice
The size of each array is fixed. Slicing provides a dynamically sized, flexible view of array elements. In practice, slicing is more common than arrays. Type []T represents a slice whose element type is T. Slices are defined by two subscripts, an upper bound and a lower bound, separated by a colon:
a[low : high]
Copy the code
It selects a half-open interval that includes the first element but excludes the last. The following expression creates a slice containing elements in a with subscripts from 1 to 3:
a[1:4]
Copy the code
Such as:
primes := [6]int{2.3.5.7.11.13}
var s []int = primes[1:4]
fmt.Println(s)
/ / [3 5 7]
Copy the code
Slicing is like a reference to an array
Slicing does not store any data; it simply describes a segment of the underlying array. Changing an element of a slice modifies the corresponding element in its underlying array. Slices that share the underlying array with it observe these changes.
func main(a) {
names := [4]string{
"John"."Paul"."George"."Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
// [John Paul George Ringo]
// [John Paul] [Paul George]
// [John XXX] [XXX George]
// [John XXX George Ringo]
Copy the code
Slice the grammar
Slicing grammar is similar to array grammar without length. This is an array grammar:
[3]bool{true.true.false}
Copy the code
The following creates the same array as above, and then builds a slice that references it:
[]bool{true.true.false}
Copy the code
Such as:
func main(a) {
q := []int{2.3.5.7.11.13}
fmt.Println(q)
r := []bool{true.false.true.true.false.true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2.true},
{3.false},
{5.true},
{7.true},
{11.false},
{13.true},
}
fmt.Println(s)
}
// [2 3 5 7 11 13]
// [true false true true false true]
// [{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
Copy the code
Default behavior for slicing
You can use its default behavior to ignore upper and lower bounds when slicing. The default value for the lower bound of the slice is 0, and the upper bound is the length of the slice. For an array of
var a [10]int
Copy the code
In terms of, the following slices are equivalent:
a[0:10]
a[:10]
a[0:]
a[:]
Copy the code
Such as:
func main(a) {
s := []int{2.3.5.7.11.13}
s = s[1:4]
fmt.Println(s)
s = s[:2]
fmt.Println(s)
s = s[1:]
fmt.Println(s)
}
/ / [3 5 7]
/ / [3 5]
/ / [5]
Copy the code
Section length and capacity
Slices have length and capacity. The length of a slice is the number of elements it contains. The size of a slice is the number from its first element to the end of its underlying array element. The length and capacity of slice S can be obtained by the expressions len(s) and Cap (s).
func main(a) {
s := []int{2.3.5.7.11.13}
printSlice(s)
// Cut the slice to a length of 0
s = s[:0]
printSlice(s)
// Extend the length
s = s[:4]
printSlice(s)
// Discard the first two values
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n".len(s), cap(s), s)
}
// len=6 cap=6 [2 3 5 7 11 13]
// len=0 cap=6 []
// len=4 cap=6 [2 3 5 7]
// len=2 cap=4 [5 7]
Copy the code
Nil slice
The zero value of the slice is nil. Nil slices have a length and capacity of 0 and no underlying array.
var s []int
// var s = []int{} assigns an empty slice
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")}/ / [] 0 0
// nil!
Copy the code
Create slices with make
Slices can be created using the built-in function make, which is how you create dynamic arrays. The make function allocates an array with zero elements and returns a slice that references it:
a := make([]int.5) // len(a)=5
Copy the code
To specify its capacity, pass a third argument to make:
b := make([]int.0.5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
Copy the code
Slice of slice
Slices can contain any type and even other slices.
func main(a) {
// Create a tic-tac-toe board
board := [][]string{[]string{"_"."_"."_"},
[]string{"_"."_"."_"},
[]string{"_"."_"."_"}},// Two players take turns typing X and O
board[0] [0] = "X"
board[2] [2] = "O"
board[1] [2] = "X"
board[1] [0] = "O"
board[0] [2] = "X"
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], ""))}}//X _ X
//O _ X
//_ _ O
Copy the code
Appends elements to slices
Appending new elements to slices is a common operation, for which Go provides a built-in append function. The documentation for the built-in functions describes this function in detail.
func append(s []T, vs ... T) []T
Copy the code
The first parameter s of append is a slice of element type T, and the remaining values of type T are appended to the end of the slice. The result of append is a slice that contains all the elements of the original slice plus the newly added element. When the underlying array of S is too small to hold all the given values, it allocates a larger array. The returned slice points to the newly allocated array. (To learn more about slicing, read the article Go Slicing: Usage and Nature.)
func main(a) {
var s []int
printSlice(s)
// Add an empty slice
s = append(s, 0)
printSlice(s)
// The slice will grow as needed
s = append(s, 1)
printSlice(s)
// Multiple elements can be added at once
s = append(s, 2.3.4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n".len(s), cap(s), s)
}
// len=0 cap=0 []
// len=1 cap=1 [0]
// len=2 cap=2 [0 1]
// len=5 cap=6 [0 1 2 3 4]
Copy the code
Range
The range form of the for loop traverses slices or maps. When a slice is iterated over using the for loop, two values are returned for each iteration. The first value is the subscript of the current element, and the second value is a copy of the element to which the subscript corresponds.
var pow = []int{1.2.4.8.16.32.64.128}
func main(a) {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
/ / 2 * * 0 = 1
/ / 2 * * 1 = 2
/ / 2 * 2 = 4
/ / 2 * * 3 = 8
/ / 2 * * 4 = 16
/ / 2 * 5 = 32
/ / 2 * * 6 = 64
/ / 2 * * 7 = 128
Copy the code
It can be ignored by assigning a subscript or value to _.
for i, _ := range pow
for _, value := range pow
Copy the code
If you only need the index, ignore the second variable.
for i := range pow
Copy the code
Mapping the Map
Mapping maps keys to values. The zero value of the map is nil. Nil maps have no keys and can’t add keys. The make function returns the mapping of the given type and initializes it for later use.
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
func main(a) {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433.74.39967,
}
fmt.Println(m["Bell Labs"])}// {40.68433 -74.39967}
Copy the code
Grammar of mapping
The grammar of a map is similar to that of a structure, but it must have a key name.
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433.74.39967,},"Google": Vertex{
37.42202.122.08408,},// Do not delete the comma, otherwise we will report syntax error: unexpected newline, expecting comma or
}
// Map [Bell Labs:{40.68433-74.39967} Google:{37.42202-122.08408}]
Copy the code
If the top-level type is just a type name, you can omit it from the element of the grammar.
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": {40.68433.74.39967},
"Google": {37.42202.122.08408}},// Map [Bell Labs:{40.68433-74.39967} Google:{37.42202-122.08408}]
Copy the code
Modify the mapping
Insert or modify elements in mapping M:
m[key] = elem
Copy the code
Get elements:
elem = m[key]
Copy the code
Delete element:
delete(m, key)
Copy the code
Check the existence of a key by double assignment:
elem, ok = m[key]
Copy the code
If key is in m, ok is true; Otherwise, ok is false. If key is not in the map, elem is the zero value of the mapping element type. Similarly, when a nonexistent key is read from a map, the result is a zero value for the element type of the map. Note: If elem or OK is not already declared, you can use a short variable declaration:
elem, ok := m[key]
Copy the code
func main(a) {
m := make(map[string]int)
m["Answer"] = 42
fmt.Println("The value:", m["Answer"])
m["Answer"] = 48
fmt.Println("The value:", m["Answer"])
delete(m, "Answer")
fmt.Println("The value:", m["Answer"])
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
}
// The value: 42
// The value: 48
// The value: 0
// The value: 0 Present? false
Copy the code
Function Func
Functions are also values. They can be passed like any other value. A function value can be used as a parameter or return value of a function.
func compute(fn func(float64.float64) float64) float64 {
return fn(3.4)}func main(a) {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5.12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
/ / 13
/ / 5
/ / 81
Copy the code
Closure of a function
The Go function can be a closure. A closure is a function value that references variables outside of its function body. The function can access and assign values to the variables it references; in other words, the function is “bound” by those variables. For example, the function adder returns a closure. Each closure is bound to its own sum variable.
func adder(a) func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main(a) {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(2 -*i),
)
}
}
/ / 0 0
// 1 -2
// 3 -6
/ / 6 to 12
/ / 10 to 20
// 15 -30
// 21 -42
// 28 -56
// 36 -72
// 45 -90
Copy the code
Methods and interfaces
methods
Go has no classes. However, you can define methods for struct types. Methods are a class of functions that take special receiver parameters. The method receiver is in its own argument list, between the func keyword and the method name. In this case, the Abs method has a receiver named V of type Vertex.
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs(a) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main(a) {
v := Vertex{3.4}
fmt.Println(v.Abs())
}
/ / 5
Copy the code
Methods are functions
Remember: a method is just a function with a receiver argument. Now Abs is written as a normal function, and it doesn’t change anything.
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main(a) {
v := Vertex{3.4}
fmt.Println(Abs(v))
}
Copy the code
You can also declare methods for non-struct types. In this case, we see a numeric type MyFloat with Abs methods. You can only declare methods for recipients of types defined in the same package, not for recipients of types defined in other packages (including built-in types like int). The receiver’s type definition and method declaration must be in the same package; Methods cannot be declared for built-in types.
type MyFloat float64
func (f MyFloat) Abs(a) float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main(a) {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
/ / 1.4142135623730951
Copy the code
Pointer receiver
You can declare methods for pointer receivers. This means that for some type T, the receiver’s type can use a *T grammar. (Also, T cannot be a pointer like *int.) For example, here we define the Scale method for *Vertex.
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs(a) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main(a) {
v := Vertex{3.4}
v.Scale(10)
fmt.Println(v.Abs())
}
/ / 50
Copy the code
Methods that pointer to the receiver can modify the value the receiver points to (as Scale does here). Because methods often need to modify their receivers, pointer receivers are more common than receivers. If a value receiver is used, the Scale method operates on a copy of the original Vertex value. (The same is true for other arguments to the function.) The Scale method must change the value of Vertex declared in the main function with a pointer acceptor.
Method and pointer redirection
Comparing the previous two programs, you’ll probably notice that a function that takes a pointer argument must accept a pointer:
var v Vertex
ScaleFunc(v, 5) // Error compiling!
ScaleFunc(&v, 5) // OK
Copy the code
When a method with a pointer receiver is called, the receiver can be both the value and pointer:
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
Copy the code
For statement v.scale (5), methods with pointer receivers can be called directly even if v is a value rather than a pointer. That is, since the Scale method has a pointer receiver, Go interprets the statement v.scale (5) as (&v).scale (5) for convenience.
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main(a) {
v := Vertex{3.4}
v.Scale(2)
ScaleFunc(&v, 10)
p := &Vertex{4.3}
p.Scale(3)
ScaleFunc(p, 8)
fmt.Println(v, p)
}
{60 80} &{96 72} returns a pointer and a value
Copy the code
The same thing happened in the opposite direction. A function that takes a value as an argument must accept a value of the specified type:
var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // Error compiling!
Copy the code
When a method with a value receiver is called, the receiver can be both the value and the pointer:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
Copy the code
In this case, the method call p.abs () is interpreted as (*p).abs ().
Select the value or pointer as the receiver
There are two reasons to use pointer receivers: First, methods can modify the values to which their receivers point. Second, it avoids copying the value each time the method is called. This is more efficient when the value is of type large structure.
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v *Vertex) Abs(a) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main(a) {
v := &Vertex{3.4}
fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
v.Scale(5)
fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}
// Before scaling: &{X:3 Y:4}, Abs: 5
// After scaling: &{X:15 Y:20}, Abs: 25
Copy the code
In this case, the Scale and Abs receiver are of type *Vertex, even though Abs does not need to modify its receiver. In general, all methods of a given type should have a value or pointer receiver, but you should not mix the two.
interface
An interface type is a collection defined by a set of method signatures. Variables of the interface type can hold any values that implement these methods.
type Abser interface {
Abs() float64
}
Copy the code
Interfaces and implicit implementations
A type implements an interface by implementing all of its methods. Since there is no special explicit declaration, there is no “implements” keyword. Implicit interfaces decouple the definition from the implementation of the interface so that the implementation of the interface can appear in any package without prior preparation. Thus, there is no need to add a new interface name to each implementation, which also encourages explicit interface definitions.
type I interface {
M()
}
type T struct {
S string
}
// This method means that type T implements interface I, but we do not need to declare this explicitly.
func (t T) M(a) {
fmt.Println(t.S)
}
func main(a) {
var i I = T{"hello"}
i.M()
}
Copy the code
The interface values
Interfaces are also values. They can be passed like any other value. The interface value can be used as a parameter or return value of a function. Internally, an interface value can be thought of as a tuple containing a value and a concrete type: (Value, type) An interface value holds a concrete value of a concrete underlying type. When an interface value calls a method, it executes a method of the same name of its underlying type.
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M(a) {
fmt.Println(t.S)
}
type F float64
func (f F) M(a) {
fmt.Println(f)
}
func main(a) {
var i I
i = &T{"Hello"}
describe(i)
i.M()
i = F(math.Pi)
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
// (&{Hello}, *main.T)
// Hello
/ / (3.141592653589793, the main F)
/ / 3.141592653589793
Copy the code
Interface value whose underlying value is nil
Even if the specific value in the interface is nil, the method will still be called by the nil receiver. In some languages, this raises a null-pointer exception, but in Go it is common to write methods to handle it elegantly (such as the M method in this case).
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M(a) {
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
func main(a) {
var i I
var t *T
i = t
describe(i)
i.M()
i = &T{"hello"}
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
// (<nil>, *main.T)
// <nil>
// (&{hello}, *main.T)
// hello
Copy the code
Note: An interface that holds a nil concrete value is not nil itself.
Nil interface values
Nil interface values hold neither values nor concrete types. Calling a method for a nil interface produces a runtime error because the tuple of the interface does not contain a type that indicates which specific method to call.
type I interface {
M()
}
func main(a) {
var i I
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
// (<nil>, <nil>) panic: runtime error: invalid memory address or nil pointer dereference
// [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x498eaf]
Copy the code
Empty interface
An interface value that specifies zero methods is called a null interface:
interface{}
Copy the code
A null interface can hold any type of value. (Because each type implements at least zero methods.) Empty interfaces are used to handle values of unknown types. For example, fmt.Print can accept any number of arguments of type interface{}.
func main(a) {
var i interface{}
describe(i)
i = 42
describe(i)
i = "hello"
describe(i)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
// (<nil>, <nil>)
// (42, int)
// (hello, string)
Copy the code
Types of assertions
Type assertions provide access to the underlying concrete values of interface values.
t := i.(T)
Copy the code
This statement asserts that the interface value I holds the concrete type T and assigns its underlying value of type T to the variable T. If I does not hold a value of type T, the statement triggers a panic. To determine whether an interface value holds a particular type, a type assertion returns two values: its underlying value and a Boolean value that reports whether the assertion was successful.
t, ok := i.(T)
Copy the code
If I holds a T, then T will be its underlying value, and OK is true. Otherwise, OK will be false and t will be zero of type T, and the program will not panic. Notice how this syntax is the same as reading a map.
func main(a) {
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
f = i.(float64) / / an error (panic)
fmt.Println(f)
}
// hello
// hello true
// 0 false
// panic: interface conversion: interface {} is string, not float64
Copy the code
Type selection
Type selection is a structure that selects branches sequentially from several type assertions. Type selection is similar to a normal Switch statement, except that cases in type selection are types (not values), and they are compared against the types of values stored for a given interface value.
switch v := i.(type) {
case T:
// v is of type T
case S:
// v is of type S
default:
V has the same type as I
}
Copy the code
The declaration in type selection has the same syntax as the type assertion I. (T), except that the concrete type T is replaced with the keyword type. This selection statement determines whether the interface value I holds a value type T or S. In the case of T or S, the variable v holds values of type T or S owned by I, respectively. By default (that is, no match), the interface type and value of the variable v and I are the same.
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T! \n", v)
}
}
func main(a) {
do(21)
do("hello")
do(true)}// Twice 21 is 42
// "hello" is 5 bytes long
// I don't know about type bool!
Copy the code
Stringer interface (similar to toString method)
Stringer, defined in the FMT package, is one of the most common interfaces.
type Stringer interface {
String() string
}
Copy the code
Stringer is a type that can describe itself as a string. FMT packages (and many more) print values through this interface.
type Person struct {
Name string
Age int
}
func (p Person) String(a) string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main(a) {
a := Person{"Arthur Dent".42}
z := Person{"Zaphod Beeblebrox".9001}
fmt.Println(a, z)
}
// Arthur Dent (42 years)
// Zaphod Beeblebrox (9001 years)
Copy the code
error
The Go program uses an error value to indicate error status. Similar to fmt.Stringer, the error type is a built-in interface:
type error interface {
Error() string
}
Copy the code
(Similar to fmt.stringer, the FMT package also prints values with error.) Normally the function returns an error, and the calling code should determine if the error is nil to handle the error.
i, err := strconv.Atoi("42")
iferr ! =nil {
fmt.Printf("couldn't convert number: %v\\n", err)
return
}
fmt.Println("Converted integer:", i)
Copy the code
Error is nil, success; Non-nil error means failure.
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error(a) string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run(a) error {
return &MyError{
time.Now(),
"it didn't work",}}func main(a) {
iferr := run(); err ! =nil {
fmt.Println(err)
}
}
// at 2009-11-10 23:00:00 +0000 UTC m=+0.000000001, it didn't work
Copy the code
Reader
The IO package specifies the IO.Reader interface, which represents reading from the end of the data stream. The Go standard library contains many implementations of this interface, including files, network connections, compression, and encryption. The IO.Reader interface has a Read method:
func (T) Read(b []byte) (n int, err error)
Copy the code
Read fills the given byte slice with data and returns the number of bytes filled and an error value. It returns an IO.eof error when it encounters the end of the data stream.
func main(a) {
r := strings.NewReader("Hello, Reader!")
b := make([]byte.8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break}}}// n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
// b[:n] = "Hello, R"
// n = 6 err = <nil> b =[101 97 100 101 114 33 32 82]
// b[:n] = "eader!"
// n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
// b[:n] = ""
Copy the code
The sample code creates a strings.Reader and reads its output at a rate of 8 bytes at a time.
concurrent
Note that map itself is not concurrency safe!
Go ride goroutine
Go programs (Goroutines) are lightweight threads managed by the Go runtime.
go f(x, y, z)
Copy the code
A new Go procedure is started and executed
f(x, y, z)
Copy the code
The evaluation of f, x, y, and z takes place in the current Go procedure, while the execution of F takes place in the new Go procedure. The Go programs run in the same address space, so they must be synchronized when accessing shared memory. The Sync package provides this capability, but it’s not often used in Go because there are other ways (see below).
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main(a) {
go say("world")
say("hello")}// world
// hello xxx
// hello xxx
// world
// world
// hello xxx
// hello xxx
// world
// world
// hello xxx
Copy the code
Channel chan
Channels are pipes with types through which you can send or receive values with the channel operator <-.
ch <- v // Send v to channel CH.
v := <-ch // Accept the value from ch and assign v.
Copy the code
(The “arrow” is the direction of the data flow.) Like maps and slicing, channels must be created before use:
ch := make(chan int)
Copy the code
By default, both send and receive operations block until the other end is ready. This allows the Go program to synchronize without explicit locks or race variables. The following example sums the numbers in a slice, dividing the task between two Go routines. Once the two Go programs have completed their calculations, it can calculate the final result.
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // Send and into c
}
func main(a) {
s := []int{7.2.8.9 -.4.0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // Receive from c
fmt.Println(x, y, x+y)
}
/ / - 5 and 12
Copy the code
Buffered channel
Channels can be buffered. Initialize a buffered channel by supplying the buffer length as the second argument to make:
ch := make(chan int.100)
Copy the code
Sending data to a channel blocks only when its buffer is full. When the buffer is empty, the receiver blocks.
func main(a) {
ch := make(chan int.23)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
/ / 1
/ / 2
Copy the code
Modify the example to fill the buffer and see what happens.
The range and close
A sender can close a channel by closing it to indicate that there are no values to send. The receiver can test whether the channel is closed by assigning a second parameter to the receive expression: if no value can be received and the channel is closed, the execution is complete
v, ok := <-ch
Copy the code
Ok will then be set to false. The loop for I := range C continues to receive values from the channel until it is turned off. Note: Only the sender can close the channel, not the receiver. Sending data to a closed channel can cause a panic. Also note that channels, unlike files, usually do not need to be closed. Closing is necessary only if the receiver must be told that there are no more values to send, such as terminating a range loop.
func fibonacci(n int, c chan int) {
x, y := 0.1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main(a) {
c := make(chan int.10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
/ / 0
/ / 1
/ / 1
/ / 2
/ / 3
/ / 5
/ / 8
/ / 13
/ / 21
/ / 34
Copy the code
The select statement
The SELECT statement enables a Go program to wait for multiple communication operations. Select blocks until a branch can continue, at which point the branch is executed. When multiple branches are ready, one is randomly selected for execution.
func fibonacci(c, quit chan int) {
x, y := 0.1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return}}}func main(a) {
c := make(chan int)
quit := make(chan int)
go func(a) {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
/ / 0
/ / 1
/ / 1
/ / 2
/ / 3
/ / 5
/ / 8
/ / 13
/ / 21
/ / 34
// quit
Copy the code
The default option
The default branch is executed when no other branch in the SELECT is ready. To avoid blocking when trying to send or receive, use the default branch:
select {
case i := <-c:
/ / use the I
default:
// Execute when receiving from c blocks
}
Copy the code
func main(a) {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(".")
time.Sleep(50 * time.Millisecond)
}
}
}
/ /.
/ /.
// tick.
/ /.
/ /.
// tick.
/ /.
/ /.
// tick.
/ /.
/ /.
// tick.
/ /.
/ /.
// tick.
// BOOM!
Copy the code
sync.Mutex
We have seen that channels are very good for communication between Go programs. But what if we don’t need to communicate? For example, what if we just wanted to ensure that only one Go program could access a shared variable at a time to avoid collisions? The concept involved here is called mutual exclusion, and we often use the data structure Mutex to provide this mechanism. The SYNc. Mutex Mutex type and its two methods are provided in the Go library:
Lock
Unlock
We can ensure mutually exclusive execution of a block of code by calling Lock before code and Unlock after code. See Inc method. We can also use the defer statement to ensure that the mutex is unlocked. See Value method.
// Concurrent use of SafeCounter is safe.
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc increments the value of the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Only one goroutine can access c.v. at a time after Lock
c.v[key]++
c.mux.Unlock()
}
// Value returns the current Value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Only one goroutine can access c.v. at a time after Lock
defer c.mux.Unlock()
return c.v[key]
}
func main(a) {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))}/ / 1000
Copy the code