Goland study notes

Posted by seco on Fri, 07 Jan 2022 04:43:56 +0100

Golang study notes

This article is a handy note when watching the video material Golang beginner tutorial from YouTube. It is only for auxiliary learning and review. Due to the limited level, there may be some translation errors and content errors. Criticism and correction are welcome.
YouTube source: Learn Go Programming - Golang Tutorial for Beginners - YouTube

1 number type

1.1 ln and f in print

Use FMT Println and FMT Printf prints an int type

var i int = 42
//Difference between ln and f
fmt.Println("%v, %T", i, i)
fmt.Printf("%v, %T\n", i, i)

Results obtained:

%v, %T 42 42                                            
42, int

f can format the output, and ln has its own line feed.

1.2 converting int type to float32 type

var j float32 = 45.2
var i int = 42
fmt.Printf("%v, %T\n", j, j)
j = float32(i)
fmt.Printf("%v, %T\n", j, j)

Results obtained:

45.2, float32                                            
42, float32 

1.3 converting float32 type to int type

var j float32 = 45.2
var i = int(j)
fmt.Printf("%v, %T\n", i, i)

Results obtained:

42, int 

The content after the decimal point is truncated.

1.4 converting int to string

//Direct conversion is ASCII
s = string(i)
fmt.Printf("%v, %T\n", s, s)

//Using strconv package transformation
s = strconv.Itoa(i)
fmt.Printf("%v, %T\n", s, s)

Results obtained:

*, string                                                
42, string

The direct conversion of int to string is the corresponding ASCII, and strconv conversion is required.

1.5 complex numbers complex64 and complex128

1.5.1 use of plural

var c1 complex64 = 2i
fmt.Printf("%v, %T\n", c1, c1)
c1 = 1 + 3i
fmt.Printf("%v, %T\n", c1, c1)

Results obtained:

(0+2i), complex64	//0 will be displayed automatically
(1+3i), complex64

1.5.2 using real and imag to obtain real and imaginary parts

//Use real and imag to pull out the real part and imaginary part. If it is complex64, two float32 will come out respectively
fmt.Printf("%v, %T\n", real(c1), real(c1))
fmt.Printf("%v, %T\n", imag(c1), imag(c1))
var c2 complex128 = 1 + 2i

//Pull out the real part and imaginary part with real and imag. If it is complex128, two float64 will come out respectively
fmt.Printf("%v, %T\n", real(c2), real(c2))
fmt.Printf("%v, %T\n", imag(c2), imag(c2))

//Another construction method is to use functions directly
var c3 complex128 = complex(5, 10)
fmt.Printf("%v, %T", c3, c3)

complex64 and complex128 are actually components that are split into two float s.

complex type explanation:

// complex64 is the set of all complex numbers with float32 real and
// imaginary parts.
type complex64 complex64

// complex128 is the set of all complex numbers with float64 real and
// imaginary parts.
type complex128 complex128

2. Boolean type

n := 1 == 1
m := 1 == 2
fmt.Printf("%v, %T\n", n, n)
fmt.Printf("%v, %T\n", m, m)

Results obtained:

true, bool
false, bool

Note that if no initial value is assigned to all data, it will be 0 or false.

3 symbolic operation

var a int = 10      //1010 > 10
var b int = 3       //0011 > 3
fmt.Println(a & b)  //0010 > 2
fmt.Println(a | b)  //1011 > 11
fmt.Println(a ^ b)  //1001 > 9
fmt.Println(a &^ b) //1000 > 8

Results obtained:

2
11
9
8

Note the binary in symbolic operations. Note that in the judgment condition of the if statement, and is & &, or |.

4 characters and strings

4.1 string

var s string = "this is a string"
fmt.Printf("%v, %T\n", s, s)

Results obtained:

this is a string, string

4.2 printed characters

//A character in a direct print string is of type uint8
fmt.Printf("%v, %T\n", s[2], s[2])

//Display the characters in the string after conversion
fmt.Printf("%v, %T\n", string(s[2]), string(s[2]))

A character in the direct print string is uint8 type and can only be displayed after conversion:

105, uint8
i, string

4.3 byte slicing and rune

//Use array a to represent s (byte slice)
var a = []byte(s)
fmt.Printf("%v, %T\n", a, a)

//Using Rune to represent a rune, the result is ASCII and int32 types
var r rune = 'a'
fmt.Printf("%v, %T\n", r, r)

Results obtained:

[116 104 105 115 32 105 115 32 97 32 115 116 114 105 110 103], []uint8
97, int32 

string type explanation:

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

The string is represented as a utf-8 character set, and the string can be empty, but cannot be a 0 value. Also note that Values of string type are immutable That is, the value of string type is immutable.

5 constant Const

5.1 defining constants

Generally, C is used to define constants:

#define MYCONST 10 

However, in GoLand, if the first letter is capitalized, it will be export ed, so the first letter capitalization is not applicable

//Constant definitions are different from other languages. Hump naming is generally used in the package
const myConst int = 42
fmt.Printf("%v, %T\n", myConst, myConst)

//If it is to be exposed to other documents, capitalize the first letter on the basis of the hump
const MyConst int = 43
fmt.Printf("%v, %T\n", MyConst, MyConst)

Results obtained:

42, int
43, int

The value of const is not allowed to be changed. And must be determined at run time, that is, function definitions cannot be used.

For example:

import "math"

func main(){
    const myConst float64 = math.Sin(1.57)
    fmt.Printf("%v, %T\n", myConst, myConst)
}

This will result in a compilation error. Because of math The sin function is not determined at runtime.

However, internal constants can change the value and type of external constants, for example:

const a int16 = 27

func main(){
    const a int = 14
    fmt.Printf("%v, %T\n", a, a)
}

The result is int instead of int16 and the value is 14 instead of 27:

14, int

Note the reuse of constants.

5.2 Untype constant

If you explicitly define a constant and operate on it with a mismatched type, the compiler will report an error, for example:

const a int = 42	//Specify as int type
var b int16 = 27
fmt.Printf("%v, %T\n", a+b, a+b)

Compiler error:

invalid operation: a + b (mismatched types int and int16)

Change constant a to a typeless constant:

const a = 42	//Delete the type assignment of int
var b int16 = 27
fmt.Printf("%v, %T\n", a+b, a+b)

Get the correct result:

69, int16

At this point, the compiler is actually doing FMT Printf ("% v,% t \ n", 42 + B, 42 + b), that is, find the position of a and replace it with the value of this constant, regardless of the type of this constant.

5.3 enumeration constants

Enumeration constants are generally used at the package level, but not at the function level.

const ai = iota

func main(){
    fmt.Printf("%v, %T\n", ai, ai)
}

ai will be automatically determined as int type, and the initial value is 0. The above code results are as follows:

0, int

5.3.1 definition iota

iota is a counter. And there is a difference between enumerating constants using constant block definitions and not using constant block definitions.

Example 1: do not use constant blocks

const ai = iota
const bi = iota
const ci = iota

func main(){
    //enumeration constant 
	fmt.Printf("%v, %T\n", ai, ai)
	fmt.Printf("%v, %T\n", bi, bi)
	fmt.Printf("%v, %T\n", ci, ci)
}

Results obtained:

0, int   
0, int   
0, int  

Example 2: using constant blocks

const (
	ai1 = iota
	bi1 = iota
	ci1 = iota
)

func main(){
    //Enumerating constants (using constant block definitions)
	fmt.Printf("%v, %T\n", ai1, ai1)
	fmt.Printf("%v, %T\n", bi1, bi1)
	fmt.Printf("%v, %T\n", ci1, ci1)
}

Results obtained:

0, int   
1, int   
2, int   

iota is changing the value of constants. Constants in the same constant block are re evaluated.

5.3.2 use of constant blocks

As you can see from the previous example, abc is defined as iota. Generally speaking, if only a is defined as iota and b and c are not defined, a compilation error without defining constants is expected, but if it is in a constant block, it will be defined automatically. As follows:

const (
	ai2 = iota
	bi2		//Not defined
	ci2		//Not defined
)

func main(){
    //Enumerating constants (defined using constant blocks and not defined by b and c)
	fmt.Printf("%v, %T\n", ai2, ai2)
	fmt.Printf("%v, %T\n", bi2, bi2)
	fmt.Printf("%v, %T\n", ci2, ci2)
}

The results obtained are:

0, int   
1, int   
2, int 

It is no different from ordinary constant block definitions.

5.3.3 in constant block_

const (
	_ = iota
	abc
	def		
)

At this time, it means_ Is a zero value, but it means that we don't care (don't use), and the compiler can discard it.

5.3.4 fixed offset

const (
	a = iota + 5
)

Results obtained:

5

As long as the definition is not a function expression, you can apply addition, subtraction, multiplication, division, remainder, etc.

5.3.5 application of iota in role setting

Define some roles and use iota to quickly obtain corresponding roles and access permissions.

//role
const (
	isAdmin = 1 << iota		//0000001
	isHeadquarters			//0000010
	canSeeFinFinancials		//0000100
	canSeeAfrica			//0001000
	canSeeAsia				//0010000
	canSeeNorthAmerica		//0100000
	canSeeSouthAmerica		//1000000
)

func main(){
    //role
	var roles byte = isAdmin | canSeeFinFinancials | canSeeNorthAmerica
	fmt.Printf("%b\n", roles)
}

Results obtained:

100101

You can also check whether you have corresponding permissions:

//Role check
fmt.Printf("Is Admin? %v\n", isAdmin&roles == isAdmin)
fmt.Printf("Is HQ? %v\n", isHeadquarters&roles == isHeadquarters)

Results obtained:

Is Admin? true
Is HQ? false  

6 array

6.1 definition and initialization

var grades [3]int
fmt.Printf("Grades: %v", grades)

Results obtained:

Grades: [0 0 0]

Initialize array:

var grades1 = [3]int{97, 85, 66}
fmt.Printf("Grades1: %v\n", grades1)

Results obtained:

Grades1: [97 85 66]

You can use "." in square brackets To imply that:

var grades2 = [...]int{100, 76, 90}
fmt.Printf("Grades1: %v\n", grades2)

be careful. The number of is a fixed 3, otherwise the compilation fails. This way can make the program more robust, because it will be updated automatically with the program without always remembering the size of the array.

Results obtained:

Grades2: [100 76 90]

6.2 string array

Define a string array with a size of 5, assign values to 0 ~ 2 array elements, and print out the size of the array.

var students [5]string
fmt.Printf("Students: %v\n", students)
students[0] = "LiMing"
students[1] = "LiHua"
students[2] = "ZhangSan"
fmt.Printf("Students:%v\n", students)
fmt.Printf("Students #1: %v\n", students[0])
fmt.Printf("Number of Students: %v\n", len(students))

Results obtained:

Students: [    ]                  
Students:[LiMing LiHua ZhangSan  ]
Students #1: LiMing               
Number of Students: 5   

Note that the array size has nothing to do with the elements of the array memory, but is related to the size defined at the beginning.

6.3 storage matrix

//matrix
var identityMatrix [3][3]int
identityMatrix[0] = [3]int{1, 0, 0}
identityMatrix[0] = [3]int{0, 1, 0}
identityMatrix[0] = [3]int{0, 0, 1}
fmt.Println(identityMatrix)

Results obtained:

[[0 0 1] [0 0 0] [0 0 0]]  

6.4 array pointer

Define a triple A and assign the value of a to b. Change the element of b[1] to 5.

a := [...]int{1, 2, 3}
b := a		//Entire array assignment
b[1] = 5
fmt.Println(a)
fmt.Println(b)

The results obtained are:

[1 2 3]                           
[1 5 3]

You can see that b copies the values of the entire a array, and a and b are not associated. If pointer assignment is used, the result will be different:

//Array pointer
ap := [...]int{1, 2, 3}
bp := &ap	//Assign the pointer of ap array to bp
bp[1] = 5
fmt.Println(ap)
fmt.Println(bp)

The results obtained are:

[1 5 3]                           
&[1 5 3] 

When you change b, you are actually changing the same basic data.

6.5 array slicing

6.5.1 basic example

as1 := [3]int{1, 2, 3}		//Specifies the size of the array
bs1 := as1
fmt.Println(as1)
fmt.Println(bs1)
fmt.Printf("Length AS1: %v\n", len(as1))
fmt.Printf("Capacity AS1: %v\n", cap(as1))

as2 := []int{1, 2, 3}		//Array size not specified (equal to slice)
bs2 := as2
fmt.Println(as2)
fmt.Println(bs2)
fmt.Printf("Length AS2: %v\n", len(as2))
fmt.Printf("Capacity AS2: %v\n", cap(as2))

Results obtained:

[1 2 3]     //The value of array a does not change with the value of array b
[1 5 3]                           
Length AS1: 3                     
Capacity AS1: 3   

[1 5 3]
[1 5 3]		//The value of array a changes with the value of array b
Length AS2: 3
Capacity AS2: 3

You can see that if you use array slicing, the value of array a will change with the value of array b. b is actually a pointer to a.

Example:

aa := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
bb := aa[:]
cc := aa[3:]	//Start with the third element (excluding this element), that is, from the 4th element to the end
dd := aa[:6]	//From the first element to the end of the sixth element (including this element), there are 6 elements in total
ee := aa[3:6]	//4th, 5th, 6th
fmt.Println(aa)
fmt.Println(bb)
fmt.Println(cc)
fmt.Println(dd)
fmt.Println(ee)

Results obtained:

[1 2 3 4 5 6 7 8 9 10]
[1 2 3 4 5 6 7 8 9 10]
[4 5 6 7 8 9 10]
[1 2 3 4 5 6]
[4 5 6]

You can slice arrays using colons. And all operations point to the same piece of basic data, that is, if one of the data is changed, all the data will be changed.

6.5.2 making slices with make

The make function and its corresponding parameters. The first is the array type, the second is the length of the array, and the third is the capacity of the array. Note that the capacity of the array is different from the length of the array.

//make function
am := make([]int, 3, 100)
fmt.Println(am)
fmt.Printf("Length AM: %v\n", len(am))
fmt.Printf("Capacity AM: %v\n", cap(am))

Results obtained:

[0 0 0]
Length AM: 3
Capacity AM: 100

6.5.3 capacity

If you do not specify a value for capacity, capacity can increase dynamically as the array expands.

//capacity
aca := []int{}
fmt.Println(aca)
fmt.Printf("Length ACA: %v\n", len(aca))
fmt.Printf("Capacity ACA: %v\n", cap(aca))

//Insert an element using append
aca = append(aca, 1)
fmt.Println(aca)
fmt.Printf("Length ACA: %v\n", len(aca))
fmt.Printf("Capacity ACA: %v\n", cap(aca))

Results obtained:

[]
Length ACA: 0
Capacity ACA: 0
[1]
Length ACA: 1
Capacity ACA: 1

Then add the array:

aca = append(aca, 2, 3, 4, 5)
fmt.Println(aca)
fmt.Printf("Length ACA: %v\n", len(aca))
fmt.Printf("Capacity ACA: %v\n", cap(aca))

Results obtained:

[1 2 3 4 5]
Length ACA: 5
Capacity ACA: 6

You can see that the values of capacity and length are different.

6.5.4 append

6.5.4.1 add some elements

If you want to add an entire array using append, you must note that the following is wrong:

aca = append(aca, []int{6, 7, 8})	//Here the array is written incorrectly
fmt.Println(aca)
fmt.Printf("Length ACA: %v\n", len(aca))
fmt.Printf("Capacity ACA: %v\n", cap(aca))

Compiler error:

cannot use []int{...} (type []int) as type int in append

To use the correct wording:

aca = append(aca, []int{6, 7, 8}...)	//... Indicates that this array is sliced separately and then stored
fmt.Println(aca)
fmt.Printf("Length ACA: %v\n", len(aca))
fmt.Printf("Capacity ACA: %v\n", cap(aca))

Results obtained:

[1 2 3 4 5 6 7 8]
Length ACA: 8
Capacity ACA: 12

It can be seen that the function of append(aca, []int{6, 7, 8}...) is completely equivalent to append(aca, 6, 7, 8).

6.5.4.2 delete some elements

You can use slicing to delete some elements of the array:

//append delete element
apd := []int{1, 2, 3, 4, 5}
fmt.Println(apd)
bpd := append(apd[:2], apd[3:]...)
fmt.Println(bpd)
fmt.Println(apd)

Results obtained:

[1 2 3 4 5]		//Elements in apd before operation
[1 2 4 5]		//Elements in bpd
[1 2 4 5 5]		//Elements in apd after operation

You can see that bpd deletes the third element. In fact, it slices the first two elements and the elements after the third element and splices them again. Because slicing operates on the same underlying data elements, this method is very sensitive and may produce some unexpected errors.

7 Map

7.1 define Map

Map is a collection of key value pairs: map ["key"] = "value"

statePpipulation := map[string]int{
	"BeiJing":  1234567,
	"ShangHai": 39459,
}
fmt.Println(statePpipulation)

Results obtained:

map[BeiJing:1234567 ShangHai:39459]

The key type of map cannot be slice:

m := map[[]int]string{}		//Cannot be [] int
fmt.Println(m)

If you define a map like this, the compiler will report an error:

invalid map key type []int

Change to array type to work normally:

m := map[[3]int]string{}	//Change to [3]int
fmt.Println(m)

Results obtained:

map[]

7.2 making a map using make

//Making a map with make
statePopulationMake := make(map[string]int)
statePopulationMake = map[string]int{
	"HangZhou": 1234567,
}
fmt.Println(statePopulationMake)

Results obtained:

map[HangZhou:1234567]

7.3 map storage features

map cannot guarantee that the results are returned in order.

//Adding content will change the overall order of the map
state := make(map[string]int)
state = map[string]int{
	"China":    1,
	"American": 2,
	"Japan":    3,
}
fmt.Println(state)
state["Africa"] = 4 //Add a content
fmt.Println(state)

Results obtained:

map[American:2 China:1 Japan:3]
map[Africa:4 American:2 China:1 Japan:3]

It can be found that the order in the map is not arranged according to 123. Moreover, after the content of Article 4 is added, the order will also change.

7.4 deletion

Use the built-in function of delete to delete:

//Delete operation
stateD := make(map[string]int)
stateD = map[string]int{
	"China":    1,
	"American": 2,
	"Japan":    3,
}
fmt.Println(stateD["Japan"]) //Print out the contents of Japan
delete(stateD, "Japan")      //Delete Contents of Japan
fmt.Println(stateD["Japan"]) //Print deleted Japan content
fmt.Println(stateD["HangZhou"]) //Print a content that does not exist in the map: HangZhou

If you output an element that has been deleted, a value of 0 will be returned:

3                                       
0  
0

To sum up, if you want to print a content that is not in the map, you will not report an error, but return a value of 0.

7.5 checking query syntax

You can use the "value, ok" syntax to query:

//Query operation
stateC := make(map[string]int)
stateC = map[string]int{
	"China":    1,
	"American": 2,
	"Japan":    3,
}
pop := stateC["C"] //Make misspelled queries
fmt.Println(pop)
pop1, ok1 := stateC["C"] //Misspelled queries, plus grammar check
fmt.Println(pop1, ok1)
pop2, ok2 := stateC["China"] //Correctly spelled queries, plus check grammar
fmt.Println(pop2, ok2)

Results obtained:

0      		//Spelling error, return 0 value               
0 false     //Spelling error, check and return false          
1 true 		//Correct spelling, check and return true

Checking syntax can be used to determine whether it exists. If you only need to check whether it exists, you can use "":

_, ok3 := state["China"] //If you only need to check whether it exists
fmt.Println(ok3)

Results obtained:

true

7.6 map is a reference type (pointer)

Like array slices, if there are multiple allocations to the same map, they all point to the same piece of basic data, that is, the map is actually a reference type, which means that if the data in the map is operated at one location, any other variables pointing to the map can see this change. It can be compared with the struct type in 8.3.

//Pointer operation
	stateP := make(map[string]int)
	stateP = map[string]int{
		"China":    1,
		"American": 2,
		"Japan":    3,
	}
	fmt.Println(stateP) //Print out the original map
	sp := stateP
	delete(sp, "Japan") //Delete pointer
	fmt.Println(stateP) //Print the original map content again
	fmt.Println(sp)     //Print the contents of sp

Results obtained:

map[American:2 China:1 Japan:3]	//Print out the original map
map[American:2 China:1]			//After deleting Japan, print the original map again
map[American:2 China:1]			//Print the contents of sp

You can see that the original map and the modified map point to the same piece of data.

8 struct

8.1 define struct

Define a struct structure with four fields, and assign values to three of them. The naming rules in sturct are the same as those of other types. If you want to export, use Pascal to name it, and if you want to import, use hump to name it.

In the following example, if the Doctor is exposed to the outside and the Number field in the structure is also exposed to the outside, only the Doctor and its Number can be seen outside, and other fields are invisible.

type Docter struct {
	Number     int		//If you want to export, use Pascal to name it
	actorName  string	//In case of import, hump naming is used
	companions []string
	episodes   []string
}

func main() {
	aDoctor := Docter{
		Number:    3,
		actorName: "Shark",
		companions: []string{
			"LiMing",
			"ZhangSan",
		},
		//The episodes field can be uninitialized, and the default value is 0
	}
	fmt.Println(aDoctor)
	fmt.Println(aDoctor.Number)	//use. Method to call the specific field content
    fmt.Println(aDoctor.companions[1])
}

Results obtained:

{3 Shark [LiMing ZhangSan] []}
3       
ZhangSan

You can also use the location method for the initial assignment of struct, but it is not recommended.

8.2 anonymous struct

The first set of curly braces defines the keywords in struct, and the second set of curly braces defines the contents in struct as initializers. Anonymous structs are usually used in very short-lived data operations, such as JSON response to network service call, etc.

//Anonymous struct
	bDoctor := struct {
		name string
	}{"LiMing"}
	fmt.Println(bDoctor)

Results obtained:

{LiMing}

The advantage of using anonymity is that you don't have to create a formal type that can be used throughout the process, but only wrap something that may only be used once.

8.3 struct is an independent value type

Different from map and slice, when passing a struct structure in an application, it refers to an independent data set. It's actually transmitting copies of the same data. If you are operating on a very, very large data set, remember that each operation creates a corresponding copy. It can be compared with the map type in 7.6.

//Anonymous struct
	bDoctor := struct {
		name string
	}{"LiMing"}
	fmt.Println(bDoctor)

	//Change the value of struct after assignment
	anotherDoctor := bDoctor
	anotherDoctor.name = "ZhangSan"
	fmt.Println(bDoctor)
	fmt.Println(anotherDoctor)

Results obtained:

{LiMing}  	//bDoctor value
{ZhangSan}	//Value of otherdoctor

You can see that the change of otherdoctor value does not affect the change of original struct value. That is, the operation of struct in one place will not affect any other variables in the application.

If you want it to point to the same piece of basic data, you can use the pointer &:

anotherDoctorP := &bDoctor	//Pointer
anotherDoctorP.name = "ZhangSan"
fmt.Println(bDoctor)
fmt.Println(anotherDoctorP)

Results obtained:

{ZhangSan} 	//bDoctor value
&{ZhangSan}	//Value of otherdoctorp

It can be seen that the change of otherdoctorp also affects the change of the original struct value.

8.4 embedding

**The go language does not support the traditional object-oriented principle** Go does not inherit, but uses a model similar to inheritance, called composition. If the two struct structures Bird and Animal are defined in the following way, they are two independent structures without association.

type Animal struct {
	Name   string
	Origin string
}

type Bird struct {
	SpeedKPH float32
	CanFly   bool
}

In the traditional object-oriented language, suppose we can define a sturuct as follows:

type Animal struct {
    Name string
    Origin string
}

type Bird struct {
    Animal animal	//Define an object animal and specify a field name for it
    SpeedKPH float32
    CanFly bool
}

If we want to call the Name field in the Animal, it is usually used as follows:

b.animal.Name

However, if we use the embedding definition, that is, we want to embed another structure in one structure, we only need to list the type of the structure to be embedded in this structure without specifying a field name:

type Animal struct {
	Name   string
	Origin string
}

type Bird struct {
	Animal		//Declare Animal directly without specifying a field name for it (embedded)
	SpeedKPH float32
	CanFly   bool
}

go will automatically process the delegation of the request for the Name field and embed the Animal type for us. There is no connection except embedding, so Bird and Animal are not traditional inheritance relationships, but a combination relationship. They cannot be used interchangeably. If you want to use data interchangeably, you need to use interfaces.

At this time, a Bird type structure b wants to call the Name belonging to the Animal type, which can be used directly as follows:

b.Name

At this point, we can regard all fields as owned by the Bird structure, and we don't have to care about the internal structure of the embedded structure.

Operate on Embedded Structures:

//Embedded structure
	b := Bird{}		//Create an empty structure and then operate
	b.Name = "LiMing"
	b.Origin = "China"
	b.SpeedKPH = 48
	b.CanFly = false
	fmt.Println(b.Name)

Results obtained:

LiMing

There is also a description of the situation, which requires a clear understanding of the internal structure. This is different from the above declaration. If you just declare an object and operate it from the outside, you don't need to know the internal structure:

//Text declaration
	c := Bird{		//Different from creating an empty structure for operation, initialization and assignment are directly performed here
		Animal: Animal{		//At this time, the internal structure of the embedded structure must be clearly known
			Name:   "LiMing",
			Origin: "China",
		},
		SpeedKPH: 48,
		CanFly:   false,
	}
	fmt.Println(c)

In the above example, the results of c and b are the same, but the situation changes when assigning values.

8.5 Tags

Add labels to describe specific information about certain fields. Suppose that in a Web application, the user needs to fill in a form. At this time, the Name field ensures that the Name is required and does not exceed the maximum length:

type AnimalTags struct {
    Name string `required max:"100"`	//The back quotation mark is not a single quotation mark. The back quotation mark is the ~ ` key in the upper left corner.
    Origin string
}

The format of the tag uses back quotation marks as separators, so that there are some key value pairs separated by spaces. The usage specified in go is to separate sub tags with spaces. If you do need a key value relationship, use a colon to separate the key from the value, and the value is usually enclosed in quotation marks.

To use tags, you need to refer to the * * "reflect" * * package in go.

import (
	"fmt"
	"reflect"	//Reference "reflect" package
)

//label
type AnimalTags struct {
	Name   string `required max:"100"`
	Origin string
}

func main() {
	//label
	t := reflect.TypeOf(AnimalTags{})	//An empty structure is created
	field, _ := t.FieldByName("Name")
	fmt.Println(field.Tag)
}

Since an object must be passed in, initialize an empty Animal type and get a field from this type. Use the FieldByName method to get the label by asking the label property of the field. The results are as follows:

required max:"100" 

Tag value itself is meaningless, so some validation libraries need to be used for parsing. We can use it to pass information to the validation framework.

9 If statement

9.1 basic grammar

Basic writing, false will not execute:

if true {
		fmt.Println("The test is true")
}

if false {
	fmt.Println("The test if false")
}

Results obtained:

The test is true

Unlike other languages, go does not allow a single line block to be used as the result of an if statement for evaluation:

if true //This is the wrong way to write
		fmt.Println("The test is true")

That is, if curly braces are not used, the compiler will report an error:

syntax error: unexpected newline, expecting { after if clause

Therefore, curly braces must be used even if there is only one line of code to execute.

9.2 general usage

The first part of the if statement is the initializer, which allows us to run a statement and generate some information to set our operations in the if block. Variables created in initialization can only be used inside the block. If variables defined in initialization are used outside the block, an undefined error will pop up.

statePopulations := map[string]int{
		"HangZhou": 123456,
		"ShangHai": 858765,
		"BeiJing":  239475,
}
if pop, ok := statePopulations["HangZhou"]; ok {	//use; Separate the two parts
	fmt.Println(pop)
}

Note that ';' is used in the if statement The results obtained by separating the front and rear parts:

123456

Here is to judge whether "HangZhou" is in the map. If the check result is yes, the corresponding value will be output.

9.3 comparison

There's nothing to say, just like other languages. As shown below, this is the simplest number guessing game:

number := 50
guess := 50
if guess < number {
    fmt.Println("Too low")
}
if guess > number {
    fmt.Println("Too high")
}
if guess == number {
    fmt.Println("You got it!")
}

AND is & &, OR is |, NOT is!.

Moreover, if it is judged that part of the | statement has returned true, it does not have to execute more code because it already knows that the test will pass. This is similar to other languages.

9.4 judging floating point numbers

You must be very careful when dealing with floating-point numbers, because floating-point numbers are an approximation of the decimal system, not an exact representation:

//Judge floating point number
	myNum := 0.123
	if myNum == math.Pow(math.Sqrt(myNum), 2) {
		fmt.Println("These are the same")
	} else {
		fmt.Println("These are different")
	}

This code is the root of 0.123, and then take its square. Generally speaking, such an operation will get the same value, but the results are different:

These are different

Therefore, when generating an error value, check whether the error value is less than a certain threshold:

//Approximate judgment floating point number
	if math.Abs(myNum/math.Pow(math.Sqrt(myNum), 2)-1) < 0.001 {
		fmt.Println("These are the same")
	} else {
		fmt.Println("These are different")
	}

The result will be a correct result, but it is not a perfect solution:

These are the same 

The key to the problem is that we must ensure that the error parameters set are small enough to capture these situations. In general, we should not compare whether two floating-point numbers are equivalent.

10 Switch statement

10.1 basic grammar

switch, like if statements, can use initialization. At the same time, note that if the tag syntax is used, the keywords between case s cannot overlap, otherwise an error will be reported:

switch i := 2 + 3; i {		//use; Separate initialized parts
case 1, 5, 10:
	fmt.Println("one, five or ten")
case 2, 4, 6:	//The meaning of no overlap means that the numbers 1, 5 and 10 in other case s cannot appear here
	fmt.Println("two, four or six")
default:
	fmt.Println("another number")
}

Results obtained:

one, five or ten

The first part is the initialization setting item, which can generate a corresponding label for us. In the above example, the value of the generated tag i is 5, which can match the content of the first case. As with the if statement, use "; "Separate the two parts.

**It is worth noting that in case, you don't have to use {} to separate a block** Because the separator in switch can ensure that it is separated correctly.

10.2 comparison

You can use comparison and logical operators:

i := 10
switch {
case i <= 10:	//The input i=10 satisfies this case
	fmt.Println("less than or equal to 10")
case i <= 20:	//The input i=10 also satisfies this case, and < = 20 also includes the case of < = 10 (overlap)
	fmt.Println("less than or equal to 20")
default:
	fmt.Println("greater tan 20")
}

Results obtained:

less than or equal to 10	//Only the first case was executed

It can be seen that when the switch satisfies both the first case and the second case, only the first case is executed. At the same time, different from the tag syntax, when using the tag list, the value range of keywords can overlap, and the first case with the value of true will be executed.

10.3 break

In other languages, you may need to explicitly place a break:

i := 10
switch {
case i <= 10:
	fmt.Println("less than or equal to 10")
        break
case i <= 20:
	fmt.Println("less than or equal to 20")
        break
default:
	fmt.Println("greater tan 20")
        break
}

But in go, break is actually implicit, because there will be correct interrupts in case s.

If you want to exit the switch in advance, you can also use break:

	j = 1
	switch j.(type) {
	case int:
		fmt.Println("i is an int")
		break
		fmt.Println("hello world")	//This statement will not be executed
	case float64:
		fmt.Println("i is a float64")
	case string:
		fmt.Println("i is string")
	case [2]int:
		fmt.Println("i is [2]int")
	default:
		fmt.Println("i is another type")
	}

Results obtained:

i is an int  

You can see that "hello world" will not be printed when the int type case is satisfied.

10.4 fallthrough

In the example of 10.2, under the condition that two cases are satisfied, we only execute the contents of the first case. If we really need to execute the second case, we can use fallthrough:

i = 10
switch {
case i <= 10:
	fmt.Println("less than or equal to 10")
	fallthrough
case i <= 20:		//At this time, i=10 satisfies case1 and case2
	fmt.Println("less than or equal to 20")
default:
	fmt.Println("greater tan 20")
}

The results obtained are:

less than or equal to 10
less than or equal to 20

However, if false through is used, the case will be executed even if the conditions of the second case are not met. Because the break is actually cancelled:

i = 10
switch {
case i <= 10:
	fmt.Println("less than or equal to 10")
	fallthrough
case i >= 20:	//At this time, i=10 meets case1 but not case2
	fmt.Println("less than or equal to 20")
default:
	fmt.Println("greater tan 20")
}

The results obtained are still:

less than or equal to 10
less than or equal to 20

If it is not to identify the use cases that need to be followed up, otherwise do not often use fall through, because this means that we must be responsible for the control flow.

10.5 special syntax (Type switches)

You can add on an empty interface (type) to implement a special syntax, that is, to obtain the basic type of the interface.

	var j interface{} = [3]int{}
	switch j.(type) {
	case int:
		fmt.Println("i is an int")
	case float64:
		fmt.Println("i is a float64")
	case string:
		fmt.Println("i is string")
	case [2]int:
		fmt.Println("i is [2]int")
	default:
		fmt.Println("i is another type")
	}

The results obtained are:

i is another type

If you change j to another value, the corresponding result will be returned:

j = 1 			-> i is an int
j = 1.1 		-> i is a float64
j = "1"			-> i is syring
j = [2]int{}	-> i is [2]int 

11 cycle

11.1 simple cycle

	for i := 0; i < 5; i++ {
		fmt.Println(i)
	}

Results obtained:

0
1
2
3
4

11.1.1 define multiple parameters

In other languages, it may be allowed to define two parameters in the initializer, but it is illegal in go:

	for i := 0, j := 0; i < 5; i++, j++ {	//i and j are defined, and loops i and j are++
		fmt.Println(i)
	}

The compiler will report an error:

syntax error: unexpected :=, expecting {

The reason is that "" can not be used to separate two statements, so i and j should be written into the same statement. In addition, incremental operations i + + and j + + are not expressions, but will be considered as two statements, and only one statement can appear in the for loop, so the way to do both is to write them into the same statement:

	for i, j := 0, 0; i < 5; i, j = i+1, j+1 {
		fmt.Println(i, j)
	}

This will work correctly:

0 0
1 1
2 2
3 3
4 4

11.1.2 in the for loop;

In addition to initializing in for, you can also initialize in other parts of the program, but note that you cannot remove the first "; ", otherwise an error will be reported:

	i := 0
	for ; i < 5; i++ {	//first; Can't lose
		fmt.Println(i)
	}

In addition, you can put the counter into the for loop, but you should also note that the second one cannot be removed "; ", otherwise an error will be reported:

	i := 0
	for ; i < 5; {	//There is the first one in the; In the case of the second; Can't lose
		fmt.Println(i)
        i++ 
	}

There is also a concise way of writing, that is, the first and second; Without writing at the same time, the compilation can be passed. At this time, the for loop performs a comparison operation:

	i := 0
	for i < 5 {		//Two; Either keep it at the same time or discard it at the same time
		fmt.Println(i)
        i++ 
	}

11.2 early exit

11.2.1 infinite break

In general, an error will be reported if an infinite loop is performed:

	i := 0
	for {	
		fmt.Println(i)
        i++ 
	}

However, in some cases, we do need to perform an infinite loop until the expected event occurs. Here, assume that the value of i is 5 and exit the loop:

	i := 0
	for {	
		fmt.Println(i)
        i++ 
        if i == 5 {
            break
        }
	}

We first saw break in switch, but break is often used in for loops, especially this infinite loop.

11.2.2 continue

The use of continue is to exit this iteration of the loop and re-enter the loop:

	for k := 0; k < 10; k++ {
		if k%2 == 0 {
			continue
		}
		fmt.Println(k)
	}

This is an example of skipping even numbers and printing only odd numbers. Results obtained:

1  
3  
5  
7  
9  

continue is not often used, but it is very useful.

11.2.3 Labels

If you simply use break, it will only jump out of the nearest loop:

	for i := 1; i <= 3; i++ {
		for j := 1; j <= 3; j++ {
			fmt.Println(i * j)
			if i*j >= 3 {
				break
			}
		}
	}

Results obtained:

1           
2           
3           
2           
4           
3

If you use the Labels tag, define a Loop here, and then break Loop, all loops including the inner and outer layers of the Loop will jump out:

Loop:
	for i := 1; i <= 3; i++ {
		for j := 1; j <= 3; j++ {
			fmt.Println(i * j)
			if i*j >= 3 {
				break Loop
			}
		}
	}

Results obtained:

1
2
3

11.3 loop traversal set

11.3.1 range

Using range for arrays and slices eliminates the need to care about their size:

	s := []int{1, 2, 3}
	for k, v := range s {
		fmt.Println(k, v)
	}

Results obtained:

0 1
1 2
2 3

Where k represents the value of the index (key), and v represents the corresponding value.

Use range for map:

	statePopulation := map[string]int{
		"BeiJing":  1234567,
		"ShangHai": 39459,
	}
	for k, v := range statePopulation {
		fmt.Println(k, v)
	}

Results obtained:

BeiJing 1234567
ShangHai 39459

Use range for Strings:

	str := "hello Go!"
	for k, v := range str {
		fmt.Println(k, v, string(v))
	}

Results obtained:

0 104 h
1 101 e
2 108 l
3 108 l
4 111 o
5 32
6 71 G
7 111 o
8 33 !

Directly outputting v will get the Unicode representation of characters, which can be converted using string.

11.3.2 use in range_

If we just want to get some values, we can use the we used before, For example, in the map sample:

	statePopulation := map[string]int{
		"BeiJing":  1234567,
		"ShangHai": 39459,
	}
	for _, v := range statePopulation {
		fmt.Println(v)
	}

Results obtained:

1234567
39459

But note that if you don't use, An error will occur.

12 control

Control Flow will change the execution flow of the program.

12.1 delay (Defer)

The delay will output after the end of the function:

	fmt.Println("1")
	defer fmt.Println("2")
	fmt.Println("3")

Results obtained:

1
3
2

In fact, when exiting the current function, check whether there are any delay functions to be called.

If there are multiple defer functions, the * * last in first out * * principle will be followed:

	defer fmt.Println("1")
	defer fmt.Println("2")
	defer fmt.Println("3")

Results obtained:

3
2
1

This design is reasonable. We may often use defer to close open resources, and it makes sense to close in the opposite order of opening resources, because a resource may depend on the resources opened before it.

In addition, defer accepts parameters directly at that time, and does not change after it changes:

	a := "1"
	defer fmt.Println(a)
	a = "2"

We might expect the result of defer to be 2, but in fact:

1

defer does not focus on changing values.

We must be careful when using defer in the loop. We must explicitly close resources directly. Otherwise, when using defer in the loop, all open resources will remain open until the end of the program, which may lead to unpredictable memory problems.

12.2 Panic

12.2.1 occurrence of Panic

In many cases, the program will fail to run. For example, the divisor is 0:

	a, b := 1, 0
	ans := a / b
	fmt.Println(ans)

You will get a Panic result:

panic: runtime error: integer divide by zero  

You can manually throw a panic:

	fmt.Println("1")
	panic("something bad happened")	//The program will exit here
	fmt.Println("2")	//The statement will not be executed

Results obtained:

1
panic: something bad happened 

The program after throwing panic will not be executed.

12.2.2 relationship between panic and defer

panic runs after defer:

	fmt.Println("1")
	defer fmt.Println("defer")		//Run defer first
	panic("something bad happened")	//Run panic again
	fmt.Println("2")

The results obtained are:

1
defer     
panic: something bad happened  

The importance of this problem is that if the application crashes, any calls that delay shutdown will be executed before panic, which can save the data well.

12.3 Recover

**recover works only in deferred** Because when the application has a panic, it will not execute any program, but will execute the defer function.

What the recover function does is that if the application does not have a panic, it will return nil. However, if nil occurs, the error panic that actually caused the application will be returned. At this time, we need to record this error.

	fmt.Println("1")
	defer func() {
		if err := recover(); err != nil {
			log.Panicln("Error:", err)
		}
	}()
	panic("something bad happened")
	fmt.Println("2")

Results obtained:

1
2022/01/04 20:30:34 Error: something bad happened
panic: something bad happened [recovered]
        panic: Error: something bad happened

If panic, defer and recover are used in the deeper function call stack, the application will only terminate the function with panic because it is already in an unreliable state that cannot run, but the application will still return to the function call stack above that calls the recover function to continue execution, because the recover function indicates that the application is in a state that can continue execution, The result was as if nothing had happened.

However, if we encounter a panic that cannot be processed and try to recover it with recover, we can still call the panic function again to re throw the panic and further manage the panic. At this time, the panic will propagate upward, and the application will not return to the upper function to continue execution.

13 Pointers

13.1 defining pointers

Without a pointer, go will create a copy of the assignment operation. If a pointer is used, the address operator & will be used to point to the same basic data. The following is a basic ex amp le:

	var a int = 42
	var b *int = &a
	fmt.Println(a, b)
	fmt.Println(&a, b)
	fmt.Println(a, *b)
	a = 27
	fmt.Println(a, *b)

Results obtained:

42 0xc000016098
0xc000016098 0xc000016098
42 42                    
27 27  

The same data changed. At the same time, the memory address can be output.

13.2 arrays and pointers

	a := [3]int{1, 2, 3}
	b := &a[0]
	c := &a[1]
	fmt.Printf("%v %p %p\n", a, b, c)

Results obtained:

[1 2 3] 0xc0000ae090 0xc0000ae098

You can see that the address difference between b and c is 4 because the int type accounts for 4 bytes.

In other languages (C or C + +), you can operate on the pointer, but you are not allowed to do so in go:

	a := [3]int{1, 2, 3}
	b := &a[0]
	c := &a[1] - 4	//Operation not allowed	
	fmt.Printf("%v %p %p\n", a, b, c)

Normally, c should get the same value as b, but the compiler reports an error:

invalid operation: &a[1] - 4 (mismatched types *int and int)

13.3 struct and pointer

Do not use pointers:

	type myStruct struct {
		foo int
	}
	var ms myStruct		//Do not use pointers
	ms = myStruct{foo: 42}
	fmt.Println(ms)

Results obtained:

{42}

Change ms to pointer type:

	type myStruct struct {
		foo int
	}
	var ms *myStruct	//Change to pointer type
	ms = &myStruct{foo: 42}
	fmt.Println(ms)

Results obtained:

&{42}

At this time, ms actually stores the address information of the structure.

To initialize a variable to a pointer that does not point to an object, you can also use the built-in new function. However, note that if you use the new function, initialization cannot be performed, and only an empty object can be created:

	//new function
	type myStruct struct {
		foo int
	}
	var ms *myStruct
	ms = new(myStruct)
	fmt.Println(ms)

The result is a 0 value:

&{0}

Note that initialization syntax cannot be used to initialize objects in the new function.

13.4 nil

In the example of 13.3, the new function is used. At this time, a zero value is obtained. At this time, the pointer does not point to any data, so the pointer is a zero value:

	type myStruct struct {
		foo int
	}
	var ms *myStruct
	fmt.Println(ms)		//Print this pointer immediately after definition
	ms = new(myStruct)
	fmt.Println(ms)

Results obtained:

<nil>
&{0}

Dereference operators actually take precedence over point operators, so generally speaking, pointers should be used as follows:

	type myStruct struct {
		foo int
	}
	var ms *myStruct
	ms = new(myStruct)

	(*ms).foo = 42
	fmt.Println((*ms).foo)

Results obtained:

42

But use (* MS) every time Foo is too troublesome. We can use ms.foo directly, which is actually a syntax sugar:

	type myStruct struct {
		foo int
	}
	var ms *myStruct
	ms = new(myStruct)

	ms.foo = 42
	fmt.Println(ms.foo)

The same results can be obtained:

42

The pointer ms does not actually have a foo field, but points to a structure that has a foo field.

13.5 slicing and pointer

An example has been done in arrays and slices before:

	a := [3]int{1, 2, 3}
	b := a
	fmt.Println(a, b)
	a[1] = 42
	fmt.Println(a, b)

Assigning a to b actually creates a copy of a for b, and the change of a's value will not affect b:

[1 2 3] [1 2 3] 
[1 42 3] [1 2 3]

If a is changed to slice, both a and b point to the same piece of basic data:

	a := []int{1, 2, 3}
	b := a
	fmt.Println(a, b)
	a[1] = 42
	fmt.Println(a, b)

Changes in the value of a will affect b:

[1 2 3] [1 2 3]  
[1 42 3] [1 42 3]

So the slice does not contain the data itself, but a pointer.

13.6 map and pointer

The example in 7.6 has shown that the map type is a pointer.

To sum up, we must be very careful when dealing with slice and map, because in the process of passing map and slice, the application may let us fall into the situation that the data changes in unexpected ways.

14 function

14.1 naming rules

The naming rule of func function is the same as that of other types. Pascal or hump case is used as the name of the function, depending on whether we want to use case to determine its visibility.

14.2 strange provisions

In other languages, the position of {} can generally be placed at will, while in go, {must be placed on the same line as func:

func main()		//If I write like this, I will report an error
{
	fmt.Println("hello shark")

}

Compiler error:

.\func.go:5:6: missing function body
.\func.go:6:1: syntax error: unexpected semicolon or newline before {

And} must be a separate line:

func main() {	//If I write this, the compiler (at least JB Golan) will automatically help me put} on the next line when compiling
	fmt.Println("hello shark")}

If you write this, the compiler (at least JB Golan) will not report an error, but will automatically put} on the next line during compilation, and then compile.

14.3 incoming parameters

Pass in a string. Note that the parameter is consistent with the normal parameter type except that var does not need to be used.

func main() {
    i := 1
	sayMessage("hello shark", i)
}

func sayMessage(msg string, idx int) {
	fmt.Println(msg, idx)
}

Results obtained:

hello shark 1

go provides some syntax sugars. For example, a more concise parameter passing method can be used to replace the lengthy parameter definition. When we define two string types, we can define them as follows:

func sayGreeting(greeting string, name string)

However, it can also be defined as follows:

func sayGreeting(greeting, name string)

14.4 use of pointers

Pass parameters without pointers:

func main() {
	name := "shark"
	useCopy(name)
	fmt.Println(name)
}

func useCopy(name string) {
	name = "ok"
	fmt.Println(name)
}

Changing values inside a function will not affect the outside of the function:

ok		//What userCopy prints out
shark  	//Mainprinted content

Pass parameters using pointers:

func main() {
	name := "shark"
	usePointer(&name)
    fmt.Println(name)
}

func usePointer(name *string) {
	*name = "ok"
	fmt.Println(*name)
}

Changing the value inside the function will affect the outside of the function:

ok	//usePointer print out
ok	//Mainprinted content

Note that if slice and map are passed, the pointer is always passed.

14.5 variable parameters

... is to tell go to accept all incoming parameters at run time and wrap them into a slice:

func main() {
	//Variable parameters
	sum("sum is ", 1, 2, 3, 4, 5)
}

func sum(msg string, values ...int) {	//Pass in a string type and an int type variable parameter
	fmt.Println(values)		//Print slice
	result := 0
	for _, v := range values {	//Calculate the value of sum
		result += v
	}
	fmt.Println(msg, result)
}

Results obtained:

[1 2 3 4 5]
sum is 15 

Note, however, that only one variable parameter can be used and must be placed at the end of all parameter declarations.

14.6 return function

In the previous example, by slightly changing the program, you can get the same result through the return function:

func main() {
	//Variable parameters
    s := sum(1, 2, 3, 4, 5)
    fmt.Println("sum is ", s)
}

func sum(msg string, values ...int) int {	//Pass in a string type and an int type variable parameter
	fmt.Println(values)		//Print slice
	result := 0
	for _, v := range values {	//Calculate the value of sum
		result += v
	}
	return result	//Return results
}

go provides a syntax sugar that can implicitly return results. We just need to tell the function where to return:

func main() {
	//Variable parameters
    s := sum(1, 2, 3, 4, 5)
    fmt.Println("sum is ", s)
}

func sum(msg string, values ...int) (result int) {	//The returned result is defined here
	fmt.Println(values)		//Print slice
							//In the above example, result: = 0 is defined here, which is no longer required in this example
	for _, v := range values {	//Calculate the value of sum
		result += v
	}
	return 	//Tells you that you are going to return here, but you don't have to specify a return name
}

14.6 throw error

package main

import "fmt"

func main() {
	d, err := divide(5.0, 0.0)		//Divide by 0
	if err != nil {		//If nil is returned, there is no error. If nil is not returned, an error message is printed
		fmt.Println(err)
		return
	}
	fmt.Println(d)
}

func divide(a, b float64) (float64, error) {
	if b == 0.0 {	//If the divisor is 0, the program ends early and an error message is returned
		return 0.0, fmt.Errorf("Cannot divide by zero")
	}
	return a / b, nil
}

Results obtained:

Cannot divide by zero

14.7 anonymous functions

The following is the simplest anonymous function:

package main

import "fmt"

func main() {
	var fun = func() {	//Define an anonymous function
		fmt.Println("hello shark")
	}
	fun()	//Execute this function
}

In the division function in 14.6, we can define the divide function inside the function as follows:

func main() {
    var divide func(float64, float64) (float64, error)
}

14.8 method

The method is similar to the writing method of function, but there are some differences:

package main

import "fmt"

type greeter struct {	//Define a structure greeter
	greeting string
	name     string
}

func (g greeter) greet() {	//Define a method that belongs to the greeter structure
	fmt.Println(g.greeting, g.name)
	g.name = "abc"		//Change the value of the name field in the greeter structure in the method
}

func main() {
	g := greeter{
		greeting: "hello",
		name:     "shark",
	}
	g.greet()	//Execution method
	fmt.Println("new name: ", g.name)
}

If you want a method to access only the data of its parent type without changing their value, you should specify the value type without using a pointer, but you should know that the cost of doing so is very high, because each call to the method will create a copy of the structure. You can see that changing the value of the structure inside the method will not change the value of the structure in main:

hello shark
new name:  shark

However, if the pointer type is passed in, the operation in the structure will change the underlying data:

func (g *greeter) greet() {	//Define a method belonging to the greeter structure, and pass in the pointer type
	fmt.Println(g.greeting, g.name)
	g.name = "abc"		//Change the value of the name field in the greeter structure in the method
}

The results obtained are:

hello shark
new name:  abc

When operating on large data structures, passing in pointers becomes very efficient because there is no need to copy an entire structure. However, it should be noted that map and slice are pointer types, so any operation on them will affect the underlying data.

Adding some methods to a structure is the most classic usage, but methods can be defined not only on the structure, but also on any type (integer, string, etc.).

15 interface

15.1 basic concepts

Let's take a look at an example of struct:

type Docter struct {
	Number     int		//If you want to export, use Pascal to name it
	actorName  string	//In case of import, hump naming is used
	companions []string
	episodes   []string
}

The interface is defined in the same way as the structure, but struct is ultimately a data container, and the interface does not describe data, but behavior:

package main

import "fmt"

type Writer interface {		//Define interface
	Write([]byte) (int, error)	//Receive byte character slice and return int and error
}

type ConsoleWriter struct {	//Define structure
}

func (cw ConsoleWriter) Write(data []byte) (int, error) {	//Definition method
	n, err := fmt.Println(string(data))
	return n, err
}

func main() {
	var w Writer = ConsoleWriter{}
	w.Writer([]byte("Hello shark"))

}

Results obtained:

Hello shark

15.2 interface combination

type Writer interface {
    Write([]byte) (int, error)
}

type Closer interface {
    Closer() error		//A closed method is defined, and the return value is an error
}

type WriterCloser interface {
    Writer
    Closer
}

A Writer interface and a Closer interface are defined. The Closer defines a closed method with a return value of an error in case of wrong use when we try. Then a composite interface WriterCloser is defined, which is similar to embedding. Another interface can be embedded in the interface.

type BufferedWriterCloser struct {	//Define structure
    buffer *bytes.Buffer	//buffer
}

func (bwc *BufferedWriterCloser) Write(data []byte) (int, error) {
	n, err := bwc.buffer.Write(data) //bytes.Buffer type read-write operation, which saves the incoming parameters into the buffer
	if err != nil {
		return 0, err
	}
	v := make([]byte, 8)       //Make slices of length 8
	for bwc.buffer.Len() > 8 { //If the length is greater than 8, output
		_, err := bwc.buffer.Read(v) //Read 8 characters
		if err != nil {              //Determine whether an error has occurred
			return 0, err
		}
		_, err = fmt.Println(string(v)) //If no error occurs, the read 8 characters are printed
		if err != nil {
			return 0, err
		}
	}
	return n, nil
}

func (bwc *BufferedWriterCloser) Close() error {
	for bwc.buffer.Len() > 0 {		//If there is still content in the buffer, 8 characters are output
		data := bwc.buffer.Next(8)
		_, err := fmt.Println(string(data))
		if err != nil {
			return err
		}
	}
	return nil
}

At the bottom level, to ensure correct initialization, define a constructor to refresh the buffer:

func NewBufferedWriterCloser() *BufferedWriterCloser {
    return &BufferedWriterCloser{
        buffer: bytes.NewBuffer([]byte{})
    }
}

main function:

func main() {
	var wc WriterCloser = NewBufferedWriterCloser()	//initialization
	wc.Write([]byte("Hello YouTube listeners, this is a test"))	//Write data
	wc.Close()	//Close processing
}

Results obtained:

Hello Yo
uTube li
steners,
 this is
 a test 

15.3 type conversion

On the basis of the previous example, add two lines of code to the main function:

	bwc := wc.(*BufferedWriterCloser)
	fmt.Println(bwc)

Results obtained:

Hello Yo
uTube li       
steners,       
 this is       
 a test        
&{0xc00010e3c0}

You can see that the type conversion was successful. If it fails, a panic will be returned.

15.3.1 air interface

var myObj interface{} = NewBufferedWriterCloser()	//An empty interface is defined
if wc, ok := myObj.(WriterCloser); ok {	//Type conversion
    wc.Write([]byte("Hello YouTube listeners, this is a test"))	//Write data
	wc.Close()	//Close processing
}
r, ok := myObj.(io.Reader)
if ok {
    fmt.Println(r)
} else {
    fmt.Println("Conversion failed")
}

Results obtained:

Hello Yo
uTube li       
steners,       
 this is       
 a test 
Conversion failed

This empty interface is for Io The type conversion of reader failed, while the type conversion of WriterCloser succeeded.

15.3.2 type switch

This is very similar to the content of 10.5. It mainly uses an empty interface. Review it again:

	var j interface{} = 0	//An empty interface is used here
	switch j.(type) {
	case int:
		fmt.Println("i is an int")
	case string:
		fmt.Println("i is string")
	default:
		fmt.Println("i is another type")
	}

Results obtained:

i is an int

15.4 values and pointers

An incomplete example is set here. All logical processing in example 15.2 is deleted to make the code more concise. You only need to pay attention to the changes of value type and pointer type:

type Writer interface {	//Don't pay attention
	Write([]byte) (int, error)
}

type Closer interface {	//Don't pay attention
	Close() error
}

type WriterCloser interface {	//Don't pay attention
	Writer
	Closer
}

type BufferedWriterCloser struct { 	//Don't pay attention
	buffer *bytes.Buffer
}

//Note that bwc here uses the value type
func (bwc BufferedWriterCloser) Write(data []byte) (int, error) {
	return 0, nil
}

//Note that bwc here uses the value type
func (bwc BufferedWriterCloser) Close() error {
	return nil
}

func main() {
    var wc WriterCloser = BufferedWriterCloser{}	//The value type can override all method sets at this time
    fmt.Println(wc)
}

Results obtained:

{<nil>}

Everything works well, but change the code a little:

type Writer interface {	//Don't pay attention
	Write([]byte) (int, error)
}

type Closer interface {	//Don't pay attention
	Close() error
}

type WriterCloser interface {	//Don't pay attention
	Writer
	Closer
}

type BufferedWriterCloser struct { 	//Don't pay attention
	buffer *bytes.Buffer
}

//Note that bwc here uses the pointer type
func (bwc *BufferedWriterCloser) Write(data []byte) (int, error) {
	return n, nil
}

//Note that bwc here uses the value type
func (bwc BufferedWriterCloser) Close() error {
	return nil
}

func main() {
    var wc WriterCloser = BufferedWriterCloser{}	//The value type cannot overwrite all method sets at this time
    fmt.Println(wc)
}

Compiler run error:

cannot use BufferedWriterCloser{} (type BufferedWriterCloser) as type WriterCloser in assignment:
	BufferedWriterCloser does not implement WriterCloser (Write method has pointer receiver)

This is because there is a problem with the method set in the interface, that is, the conversion of a single value type cannot cover all its method sets (including value type and pointer type). If you change the code in the main function to:

func main() {
	var wc WriterCloser = &BufferedWriterCloser{}	//The type here has been changed&
	fmt.Println(wc)
}

Get a correct result:

&{<nil>}

At this time, modify the type in the method again. For better observation, only the modified part is released:

//Note that in the above example, bwc here uses the pointer type and is changed to the value type again
func (bwc BufferedWriterCloser) Write(data []byte) (int, error) {
	return n, nil
}

//Note that bwc here uses the value type
func (bwc BufferedWriterCloser) Close() error {
	return nil
}

func main() {
	var wc WriterCloser = &BufferedWriterCloser{}	//Note that the type here is not changed back
	fmt.Println(wc)
}

The correct results can be obtained:

&{<nil>}

Indicates that pointer types can override all method sets of value types.

At this time, modify the type in the method again. For better observation, only the modified part is released:

//Note that bwc here uses the pointer type
func (bwc BufferedWriterCloser) Write(data []byte) (int, error) {
	return n, nil
}

//Note that bwc here uses the pointer type
func (bwc BufferedWriterCloser) Close() error {
	return nil
}

func main() {
	var wc WriterCloser = &BufferedWriterCloser{}	//Note that the type here is not changed back
	fmt.Println(wc)
}

The correct results can be obtained:

&{<nil>}

Note pointer type can override all method sets of value type and pointer type.

So far, it can be concluded that:

① If only the value type is used in the method set, the value type or pointer type can be used during type conversion;

② If only pointer type is used in the method set, only pointer type can be used during type conversion;

③ If not only the value type but also the pointer type is used in the method set, only the pointer type can be used during type conversion;

16 Goroutine

16.1 create Goroutine

Create a green thread by typing the keyword "go" in front of the function. In go, instead of creating a huge heavy overhead thread, it creates an abstraction of a thread called goroutine:

package main

import "fmt"

func sayhello() {
	fmt.Println("Hello")
}

func main() {
	go sayhello()
}

However, in the above example, "Hello" cannot be printed, because the application will exit after main generates a goroutine, so the function has no time to print its message at all. It is necessary to delay the main process for a while:

package main

import (
	"fmt"
	"time"
)

func sayhello() {
	fmt.Println("Hello")
}

func main() {
	go sayhello()
	time.Sleep(100 * time.Millisecond)	//delay
}

Use the anonymous function to print Hello, but before sleeping, change the value of msg to Goodyear:

func main() {
	var msg = "Hello"
	go func(msg string) {
		fmt.Println(msg)
	}(msg)
	msg = "Goodbye"
	time.Sleep(100 * time.Millisecond)
}

The results obtained are:

Goodbye

It can be found that goroutine does not interrupt the main thread until it encounters a sleep call. This means that even if the main function starts a goroutine, it does not actually execute immediately, but continues to run the main function, and the content to be printed is reassigned next, and the printed value is modified, which actually creates the so-called competitive condition.

To avoid this situation, you can directly transfer parameters:

func main() {
	var msg = "Hello"
	go func(msg string) {
		fmt.Println(msg)
	}(msg)
	msg = "Goodbye"
	time.Sleep(100 * time.Millisecond)
}

This is a result before modification:

Hello

This method is actually to couple the msg variable in the main function with the go routine, because the message to be printed is actually a copy created during delivery, which is usually used when transmitting data.

Now, this go routine works well, but it's not a best practice because it uses a sleep call function. We actually bind application performance and application clock cycles to the actual world clock, which is very unreliable. So in order to solve this problem, we can actually restructure the right of use.

16.2 Synchronization mechanism

16.2.1 WaitGroups

Create a variable and extract it from the synchronization package. The purpose of weight groups is to synchronize multiple go routines together. After adding the go routine to the weight reorganization, you do not need the sleep function, but replace it with waiting for the completion of the weight reorganization. In the go routine, you can tell the weight group that it has actually completed its execution:

var wg = sync.WaitGroup{}

func main() {
	var msg = "Hello"
	wg.Add(1) //Increase right reorganization
	go func(msg string) {
		fmt.Println(msg)
		wg.Done() //Inform that the reorganization has been completed
	}(msg)
	msg = "Goodbye"
	wg.Wait() //Wait for the thread to complete
}

Results obtained:

Hello

The result is the same as the above example, but it no longer depends on the rolling clock, but takes enough time to complete and then close.

16.2.2 Mutexes

In the following example, you loop ten times, but you actually create twenty go routines, because each loop adds two to the weight reorganization.

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{}
var counter = 0 //Counter

func sayhello() { //print counter 
	fmt.Printf("Hello #%v\n", counter)
	wg.Done()
}

func increment() { //Counter increment
	counter++
	wg.Done()
}

func main() {
	for i := 0; i < 10; i++ {
		wg.Add(2) //Add two threads at a time
		go sayhello()
		go increment()
	}
	wg.Wait()
}

Generally speaking, we should expect to get such a result, that is, "Hello #0", "Hello #1"... Output sequentially and incrementally, but we get a bad result, and the result will change every time we run:

Hello #0
Hello #4
Hello #5
Hello #6
Hello #7
Hello #2
Hello #8
Hello #8
Hello #9
Hello #2

This is because what happens here is actually caused by the competition between go routines and the lack of synchronization between go routines. Each go routine is just to complete the task as quickly as possible. Therefore, in order to solve this problem, we need to use the concept of mutual exclusion.

Introduce a simple read-write mutex

package main

import (
	"fmt"
	"runtime"
	"sync"
)

var wg = sync.WaitGroup{}
var counter = 0 //Counter
var m = sync.RWMutex{}

func main() {
	runtime.GOMAXPROCS(100)
	for i := 0; i < 10; i++ {
		wg.Add(2) //Add two threads at a time
		m.RLock() //Lock
		go sayhello()
		m.Lock() //Lock
		go increment()
	}
	wg.Wait()
}

func sayhello() { //print counter 
	fmt.Printf("Hello #%v\n", counter)
	wg.Done()
	m.RUnlock() //Unlock
}

func increment() { //Counter increment
	counter++
	m.Unlock() //Unlock
	wg.Done()
}

Get the correct result:

Hello #0
Hello #1
Hello #2
Hello #3
Hello #4
Hello #5
Hello #6
Hello #7
Hello #8
Hello #9

However, this application raises new problems, because we have basically completely destroyed concurrency and parallelism in this application. Because all mutexes are forcing data to be synchronized and run in a single thread, the performance of this process may be worse than that of no go program, because we need additional overhead to deal with these mutexes.

16.3 Parallelism

This simple program can return the number of cores available to the operator on the machine:

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Printf("Thread: %v", runtime.GOMAXPROCS(-1))

}

Results obtained:

16

17 Channel

17.1 basic concepts

Channel is created using the built-in make function, uses the channel keyword to indicate that a channel is to be created, and specifies the type of data flow flowing through it. The type of the channel is strongly typed, which means that if int is specified, only integers can be sent through the channel.

The goroutine of two anonymous functions is defined. The first go routine is an anonymous function, which receives data from the channel; The second routine will actually become the sender:

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{}

func main() {
	ch := make(chan int) //Create a channel, and the specified data stream is int
	wg.Add(2)
	go func() { //Receive the data in channel to i and print its value
		i := <-ch
		fmt.Println(i)
		wg.Done()
	}()
	go func() {
		i := 42
		ch <- i //Enter data into channel
		i = 27  //Change the value of i to see if it affects the data in the channel
		wg.Done()
	}()
	wg.Wait()
}

The results obtained are:

42

i=42 is set at the beginning, and then i=27 is reassigned after i is passed in, but it does not affect the value transmitted into the channel, because like all other variables, when we pass data into the channel, we actually pass a copy of the data, and the receiving go routine does not care whether we change the variable.

Another common use case of goroutine is that if there is data to be processed asynchronously, we may want to have a different number of go routines to send the data to the channel, and make a small modification to the above program:

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{}

func main() {
	ch := make(chan int) //Create a channel, and the specified data stream is int
	for j := 0; j < 5; j++ {
		wg.Add(2)
		go func() { //Receive the data in channel to i and print its value
			i := <-ch
			fmt.Println(i)
			wg.Done()
		}()
		go func() {
			ch <- 42 //Enter data into channel
			wg.Done()
		}()
	}
	wg.Wait()
}

In fact, the go routine is created in this loop, where 10 goroutine s, five senders and five receivers are generated, and all of them will use this single channel to convey their information:

42
42
42
42
42

In this case, it can run correctly, but if the sender is placed outside the loop, an error will occur:

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{}

func main() {
	ch := make(chan int) //Create a channel, and the specified data stream is int
    
    //The sender is outside the loop
	go func() {          //Receive the data in channel to i and print its value
		i := <-ch
		fmt.Println(i)
		wg.Done()
	}()
    
	for j := 0; j < 5; j++ {
		wg.Add(2)
		go func() {
			ch <- 42 //The execution of the go routine is actually suspended on this line until space is available in the channel
			wg.Done()
		}()
	}
	wg.Wait()
}

The results obtained are:

42
fatal error: all goroutines are asleep - deadlock!

The reason for the error is that there are only five senders and only one receiver, so we can only receive one message, and then cause a deadlock. Because the sender tries to put the message into the channel, but the unbuffered channel is used by default, the second sending go routine will be blocked because nothing in our application will receive the message, Then go kills the process because it realizes there is a problem.

17.2 restricted data flow

We can design the go routine to process channel data in only one direction. The following example uses two go routines to input data into the channel and extract data from the channel respectively:

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{}

func main() {
	ch := make(chan int) //Create a channel, and the specified data stream is int
	wg.Add(2)
	go func() {
		fmt.Println(<-ch) //Fetch data from channel
		ch <- 27          //Enter data into channel
		wg.Done()
	}()
	go func() {
		ch <- 42          //Enter data into channel
		fmt.Println(<-ch) //Fetch data from channel
		wg.Done()
	}()
	wg.Wait()
}

Results obtained:

42
27

You can see that synchronization is achieved using channel. That is, if the second routine has not put data 42 into the channel, the first routine will be blocked when it wants to take out data until 42 and 27 are put into the channel in turn.

Based on the previous example, modify the channel to read-only and write only:

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{}

func main() {
	ch := make(chan int) //Create a channel, and the specified data stream is int
	wg.Add(2)
	go func(ch <-chan int) { //Receiving channel
		fmt.Println(<-ch) //Fetch data from channel
		ch <- 27          //Enter data into channel
		wg.Done()
	}(ch)
	go func(ch chan<- int) { //Send channel (data put in)
		ch <- 42          //Enter data into channel
		fmt.Println(<-ch) //Fetch data from channel
		wg.Done()
	}(ch)
	wg.Wait()
}

When running the program, the compiler will report an error:

invalid operation: ch <- 27 (send to receive-only type <-chan int)
invalid operation: <-ch (receive from send-only type chan<- int)

The first error is that we try to put data in the receive only channel, which is invalid; Similarly, the second error is trying to receive data (send only) in the send channel.

17.3 Buffered

For the deadlock problem caused by five senders and only one receiver in 17.1, buffer can be used to solve it. First, simplify the for loop problem:

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{}

func main() {
	ch := make(chan int) //Create a channel, and the specified data stream is int
	wg.Add(2)
	go func(ch <-chan int) { //Receiving channel
		fmt.Println(<-ch) //Fetch data from channel
		wg.Done()
	}(ch)
	go func(ch chan<- int) { //Send channel (data put in)
		ch <- 42 	//Put two data 42 and 27 into the channel
		ch <- 27	//This data cannot be processed and will cause an error
		wg.Done()
	}(ch)
	wg.Wait()
}

Here, five senders are simplified. Instead, two data are put into the channel and taken out only once, which is equivalent to multiple senders and one receiver. You can see that the output is deadlock as before:

42
fatal error: all goroutines are asleep - deadlock!

To solve this problem, you need to enter the second parameter when creating a channel, that is, add a buffer:

ch := make(chan int, 50) //Create a channel. The specified data stream is int and the buffer size is 50

Results obtained:

42

However, it is noted that there is still a problem because one piece of data (27) is lost. It solves a problem in a sense, but creates a new problem. But this is not caused by buffering.

The real design purpose of buffer channel is that if the sender or receiver operates at different frequencies on both sides, we can retrieve data from the sensor and solve burst transmission. The receiver can receive a certain amount of data and process it without blocking the sender during processing.

17.4 circulation

In the channel loop, the situation becomes slightly different. Review how to use range in the array in section 11.3.1:

	s := []int{1, 2, 3}
	for k, v := range s

The first parameter k is the index and the second parameter v is the value. In the channel, a parameter is directly used to represent the obtained value:

	go func(ch <-chan int) { //Receiving channel
		for i := range ch {
			fmt.Println(i) //Fetch data from channel
		}
		wg.Done()
	}(ch)

In the buffering problem of two senders and one receiver in 17.3, we used buffering to solve a deadlock problem, but caused data loss. Now we try to replace the receiver with the above cyclic code to obtain the value in the channel:

42
27
fatal error: all goroutines are asleep - deadlock!

All the data was taken out normally, but the deadlock problem occurred again. The cause of the problem is the deadlock in the loop, because in addition to fetching data, the routine continues to listen to other messages. The routine does not know how to exit the loop range, resulting in a deadlock problem. Therefore, the closed channel must be used to terminate the cycle.

17.4 closing

After the sender finishes sending data, close this channel, and the receiver's loop will detect this:

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{}

func main() {
	ch := make(chan int, 50) //Create a channel, and the specified data stream is int
	wg.Add(2)
	go func(ch <-chan int) { //Receiving channel
		for i := range ch {
			fmt.Println(i) //Fetch data from channel
		}
		wg.Done()
	}(ch)
	go func(ch chan<- int) { //Send channel (data put in)
		ch <- 42 //Put two data into the channel
		ch <- 27
		close(ch)	//Close channel
		wg.Done()
	}(ch)
	wg.Wait()
}

The correct results can be obtained:

42
27

At the same time, you can also use detection statements to manually control exit:

	go func(ch <-chan int) { //Receiving channel
		for {
            if i, ok := <-ch; ok{
                fmt.Println(i) //Fetch data from channel
            } else {
                break
            }
		}
		wg.Done()
	}(ch)

17.6 Select

In the following example, an additional channel is created. The type signature of this channel is strongly typed into a structure without fields. The characteristic of the structure without fields is that zero memory allocation is required in go. Its purpose is to realize the signal and let the receiver know that the message has been sent. Of course, you can also change the type signature of this channel to bool, but using a fieldless structure will save some memory allocation, which is a very common usage.

package main

import (
	"fmt"
	"time"
)

const (
	logInfo    = "INFO"
	logWarning = "WARNING"
	logError   = "ERROR"
)

type logEntry struct {
	time     time.Time
	severity string
	message  string
}

var logCh = make(chan logEntry, 50)
var doneCh = make(chan struct{}) //The type signature of this channel is strongly typed into a structure without fields

func main() {
	go logger()
	logCh <- logEntry{time.Now(), logInfo, "starting"}
	logCh <- logEntry{time.Now(), logInfo, "down"}
	time.Sleep(100 * time.Millisecond)
	doneCh <- struct{}{}
}

func logger() { //Listen for messages from the channel and print them
	for {
		select {
		case entry := <-logCh:
			fmt.Printf("%v - [%v]%v\n", entry.time.Format("2006-01-01T15:04:05"), 
                       entry.severity, entry.message)
		case <-doneCh:
			break
		}
	}
}

When the select has no default, if the condition is not met, it will be blocked all the time. But if you want a non blocking select statement, you need to add default.

Topics: Go IntelliJ IDEA Kubernetes Back-end