Go Basics

Posted by netmastan on Wed, 05 Jan 2022 00:53:45 +0100

Go language introduction

The difference between interpretive language and compiled language.

Go language is a compiled language.

Go language features (with garbage collection, naturally supporting concurrency)

  • Concise grammar
  • High development efficiency
  • Good execution performance

Go dependency management

Why dependency management

In the earliest days, all the third-party libraries relied on by Go were in the GOPATH directory, which led to the fact that the same library can only be saved in one version of code. If different projects depend on different versions of the same library, there is no way.

godep

The vendor mode has been introduced since 1.5. If there is a vendor directory under the project directory, the go tool chain will preferentially use the packages in the vendor for compilation and testing.

godep is a third-party dependency management tool of Go language implemented through vender mode, similar to the package management tool dep maintained by the community.

Install godep

go get github.com/tools/godep

Enter godep at the terminal to view all supported commands

Using the godep save command, two folders, Godeps and vender, will be created in the current project. There is a Godeps under the Godeps folder JSON file, which records the package information the project depends on. The vender folder is the source code file of the package that the project depends on.

vender mechanism

Go1. Supported after version 5, it can control the priority of relying on the package search path when compiling go language programs.

For example, to find a dependent package of the project, you will first find it in the vender folder under the project root directory. If you don't find it, you will find it in the $GOAPTH/src directory.

Packages and files

In Go language, if a package is imported but not used, it will be treated as a compilation error. This mandatory rule can effectively reduce unnecessary dependencies. goimports tool can automatically add or delete imported packages as needed. Many editors can integrate this tool, save files and run automatically. gofmt tool can be used to format Go files.

Package initialization first solves the dependency order of package level variables, and then initializes in the order of package level variable declaration.

The package generally contains multiple go source files, which will be initialized in the order sent to the compiler. The go language construction tool will first sort the go files according to the file name, and then call the compiler to compile in turn.

init initialization function

Each file can contain multiple init initialization functions. Except that such initialization functions cannot be called or referenced, other behaviors are similar to ordinary functions. The init initialization function in each file is automatically called when the program starts executing in the order they declare.

func init(){
    
}

Each package will only be initialized once. If p package imports another package p, p must have been initialized when q it is initialized. So the main package is initialized last.

variable and constant

Only the declaration of identifier (variable, constant, function, type) can be placed outside the function.

Go language must be declared before use.

The value of a constant expression is evaluated at compile time, not at run time, and the underlying type of each constant is a base type or number. Once a constant is assigned a value that cannot be modified, the value does not change.

identifier

In programming language, identifier is a word with special meaning defined by programmer, such as variable name, constant name, function name and so on. In Go language, identifiers are composed of alphanumeric and_ (underline) and can only be composed of letters and_ start. For example: ABC_ 123, a123.

keyword

Keyword refers to a predefined identifier with special meaning in the programming language. Keywords and reserved words are not recommended as variable names.

There are 25 keywords in Go language:

    break        default      func         interface    select
    case         defer        go           map          struct
    chan         else         goto         package      switch
    const        fallthrough  if           range        type
    continue     for          import       return       var

In addition, there are 37 reserved words in the Go language.

    Constants:    true  false  iota  nil

        Types:    int  int8  int16  int32  int64  
                  uint  uint8  uint16  uint32  uint64  uintptr
                  float32  float64  complex128  complex64
                  bool  byte  rune  string  error

    Functions:   make  len  cap  new  append  copy  close  delete
                 complex  real  imag
                 panic  recover

variable

The variable declaration starts with the keyword var, the variable type is placed after the variable, and there is no semicolon at the end of the line. for instance:

// Standard statement
var studentName string

// Batch declaration
var (
	age  int
	isOk bool
)

Initialization of variables

When the Go language declares a variable, it will automatically initialize the memory area corresponding to the variable. Each variable is initialized to the default value of its type. For example, the default value of integer and floating-point variables is 0. The default value of a string variable is an empty string. Boolean variables default to false. The default of slice, function and pointer variables is nil. When it appears on the right side of the assignment statement, it does not necessarily produce two results, or it may produce two results. For the case where the value produces a result, a zero value will be returned when the map lookup fails, a run-time panic exception will be sent when the type assertion fails, and a zero value will be returned when the channel reception fails (blocking is not a failure).

var name string = "Q1mi"
var age int = 18
var name, times = "Q1mi", 20

We can directly omit the type of the variable. At this time, the compiler will deduce the type of the variable according to the value on the right of the equal sign to complete the initialization.

var name = "Q1mi"
var age = 18

Short variable declaration

Inside the function, variables can be declared and initialized in a simpler way: =, and short declarations cannot be used outside the function.

func testNum() {
	n := 10
	m := 200 // The local variable m is declared here
	fmt.Println(m, n)
}

Anonymous variable

When using multiple assignment, if you want to ignore a value, you can use_ Receive, Represents an anonymous variable.

Anonymous variables do not occupy namespaces and do not allocate memory, so there are no duplicate declarations between anonymous variables. In lua programming language, anonymous variables are also called dummy variables.

func foo() (int, string) {
	return 100, "hehe"
}
func main() {
	// _  Anonymous variable, equivalent to placeholder
	_, str := foo()
	fmt.Println(str)
}

matters needing attention:

  1. Every statement outside a function must start with a keyword (var, const, func, etc.)
  2. : = cannot be used outside a function.
  3. _ Mostly used for placeholders, indicating that the value is ignored.
  4. After the local variable is declared, it must be used, otherwise it will compile and report an error.

type

The final value of the variable on both sides of the assignment statement must have the same data type.

The newly named types provide a way to split types of different concepts so that they are incompatible even if their underlying types are the same.

type Type name underlying type
type num int

The statement of type declaration generally appears at the package level. If the initial letter of the newly created type name is capitalized, it can also be used in the external package.

For Chinese characters, Unicode marks are treated as lowercase letters, so Chinese naming cannot be exported by default.

For each type T, there is a type conversion operation T(x) to convert x to type T. If it is pointer intensity, t may be wrapped in parentheses, (* int) (0). This transformation is allowed only when the underlying base types of the two types are the same, or both are pointer types pointing to the same underlying structure. These conversion values change the type without affecting the value itself.

If two values have different types, they cannot be compared directly. Compilation will report errors.

var	c	Celsius 
var	f	Fahrenheit
fmt.Println(c	==	0)										//	"true"
fmt.Println(f	>=	0)										//	"true"
fmt.Println(c	==	f)										//	compile	error:	type	mismatch
fmt.Println(c	==	Celsius(f))	//	"true"!

constant

For variables, constants are constant values, which are mostly used to define those values that will not change during program operation. The declaration of constants is very similar to that of variables, except that var is replaced by const, and constants must be assigned values when they are defined.

const pi = 3.1415
const e = 2.7182

After the constants pi and e are declared, their values can no longer change during the whole program run.

Multiple constants can also be declared together:

const (
    pi = 3.1415
    e = 2.7182
)

When const declares multiple constants at the same time, if the value is omitted, it means that it is the same as the value in the previous line. For example:

const (
    n1 = 100
    n2
    n3
)

In the above example, the values of constants n1, n2 and n3 are all 100.

Constant counter iota

iota is a constant counter of go language, which can only be used in constant expressions.

Iota will be reset to 0 when const keyword appears. Adding a line of constant declaration in const will make iota count once. Using iota can simplify the definition and is very useful in defining enumeration.

const (
	n1 = iota //0
	n2        //1
	_
	n4 //3
	_  = iota
	KB = 1 << (10 * iota)
	MB = 1 << (10 * iota)
	GB = 1 << (10 * iota) //It will overflow from here
	TB = 1 << (10 * iota)
	PB = 1 << (10 * iota)
)

func main(){
    fmt.Println(n1)
	fmt.Println(n2)
	fmt.Println(n4)
	fmt.Println(KB)
	fmt.Println(MB)
}
// output
0
1
3
1125899906842624
1152921504606846976

Binary output

%d: Decimal system

%o: Octal

%x: Hex

%b: Binary

%T: Output data type

%c: Character

%s: String

%p: Pointer

%v: Value

%#v: Detailed value

%f: Floating point number

%t: bool value

func main() {

	i2 := 077
	fmt.Printf("decimal system:%d\n", i2)
	fmt.Printf("Binary:%b\n", i2)
	fmt.Printf("octal number system:%o\n", i2)
	fmt.Printf("hexadecimal:%x\n", i2)
	fmt.Printf("Data type:%T\n", i2)
	//Cast type
	fmt.Printf("%d", int32(i2))
}
/**Decimal: 63
 Binary: 111111
 Octal: 77
 Hex: 3f
 Data type: int
63**/

data type

uint8Unsigned 8-bit integer (0 to 255)
uint16Unsigned 16 bit integer (0 to 65535)
uint32Unsigned 32-bit integer (0 to 4294967295)
uint64Unsigned 64 bit integer (0 to 18446744073709551615)
int8Signed 8-bit integer (- 128 to 127)
int16Signed 16 bit integer (- 32768 to 32767)
int32Signed 32-bit integer (- 2147483648 to 2147483647)
int64Signed 64 bit integer (- 9223372036854775808 to 9223372036854775807)

Special integer

typedescribe
uintuint32 on a 32-bit operating system and uint64 on a 64 bit operating system
intIt is int32 on 32-bit operating system and int64 on 64 bit operating system
uintptrUnsigned integer used to hold a pointer

Note: when using int and uint types, you cannot assume that they are 32-bit or 64 bit integers, but consider the possible differences between int and uint on different platforms.

The length returned by the built-in len() function to obtain the object length can vary according to the byte length of different platforms. In actual use, the number of elements of slices or map s can be expressed by int. however, when binary transmission is carried out and the structure description of the file is read and written, in order to keep the file structure from being affected by the byte length of different compilation target platforms, Do not use int and uint.

Floating point type

The Go language supports two floating-point types: float32 and float64. The data formats of these two floating-point types follow the IEEE754 standard. The maximum range of floating-point numbers of float32 is 3.4e38. You can use the constant definition: math_ The maximum range of maxfloat32 and float64 floating point numbers is about 1.8e308. You can use the constant definition: math MaxFloat64. The default is the float64 bit type.

%f: Represents the floating point output type

In addition to providing a large number of commonly used mathematical functions, the math package also provides the creation and testing of special values defined in the IEEE754 floating point standard: positive infinity and negative infinity, which are used to represent too large overflow numbers and the result of dividing by zero respectively, and NaN represents invalid division operation 0 / 0 or Sqrt(-1)

NaN is not equal to any number

Array is a value type, and the value is passed (copy it, modify it on the new data, and the original data will not be affected)

complex

complex64 and complex128 correspond to float32 and float64 respectively. The built-in complex function is used to construct complex numbers. The built-in real and image functions return the real and imaginary parts of complex numbers respectively.

var	x	complex128	=	complex(1,	2)	//	1+2i 
var	y	complex128	=	complex(3,	4)	//	3+4i
fmt.Println(x*y)																	//	"(-5+10i)"
fmt.Println(real(x*y))											//	"-5"
fmt.Println(imag(x*y))											//	"10"

Plural numbers can also be compared and are equal only when their real and imaginary parts are all the same.

Simplified declaration of complex numbers

x:=1+2i
y:=3+4i

character string

String is an immutable sequence of bytes. String can contain any data, including byte value 0, but it is usually used to contain human readable text. Text strings are usually interpreted as a sequence of Unicode code points encoded in UTF-8.

len(s) will return the number of bytes in the string s. strings can be connected by "+" because the string is immutable and cannot be modified. for example

s[0]='L'// compile	error:	cannot	assign	to	s[0]

Invariance means that it is also safe if two strings share the same underlying data, which makes it cheap to copy strings of any length.

Native string: `... `, use back quotation marks instead of double quotation marks. In the native string, there is no escape operation. All the contents are literal, including backspace and line feed. Native string literals are convenient for writing regular expressions because regular expressions often contain a lot of backslashes. It is also applicable to HTML template, JSON return format, command line information and multi line scenarios that need to be extended.

The source file of Go language adopts UTF-8 encoding, and Go language handles UTF8 encoded text very well, including functions related to character processing. Unicode/uft8 package provides UTF8 encoding and decoding functions for rune character sequences.

Many Unicode characters are difficult to enter directly from the keyboard, and many other characters have similar structures.

When processing strings, the range loop of go language will automatically implicitly decode UTF8 strings. The size of Chinese in the string is 3 bytes.

If you see a black hexagon or diamond icon in the output result, the decoding fails because there is an incorrect encoding in the input string.

array

The length of the array is a component of the array type. For example, [3]int and [4]int are two different array types. The length of the array must be a constant expression, because the length of the array needs to be determined at the compilation stage.

The array can be accessed by subscript. The subscript is from 0-n-1, and N represents the length of the array

By default, all elements in the array are initialized to zero.

If the form [...] appears, it indicates that the length of the array is determined according to the number of initialization values.

func initArray() {
	// By default, each element in the array is initialized to zero
	var array1[3] int=[3]int{1,2,3}
	for i, v := range array1 {
		fmt.Println(i,v)
	}
	//If [...] This form indicates that the length of the array is determined by the number of initialization values
	queue:=[...]int{5,4,3,2,1}

	fmt.Printf("%T\n",queue)
	fmt.Println(queue)


}

section

Array slicing is similar to a pointer to an array. In fact, it has its own data structure. Slice represents a variable length sequence in which each element has the same type. A slice is generally written as [] t, where T represents the type of element in slice.

  • A pointer to the native array;
  • Number of elements in array slice
  • Array slice allocated storage space

How to create array slices

  • Array based

Slice is a reference type. The underlying pointer to the array. If the elements in the array are changed, the slice will also change.

Slice operation s[i:j], where 0 ≤ i ≤ j ≤ cap(s), is used to create a new slice, referencing the subsequence of s from the ith element to the j-1 element. The new slice will have only j-i elements. If the index of position i is omitted, it will be replaced by 0, and if the index of position j is omitted, it will be replaced by len(s). Therefore, the months[1:13] slicing operation will reference all valid months, which is equivalent to the months[1:] operation; Months [:] slicing refers to the entire array.

func main() {
	//Definition of slice
	a:=[5]int{5,66,77,88,99}
	b:=a[:4] //The slices were closed before and opened after [5, 66, 77, 88]
	fmt.Println(b)
	fmt.Printf("slice type is %T\n",b)
	fmt.Printf("b:len=%d,cap=%d\n",len(b),cap(b))
	//If you modify the value of the original array, the slices referencing the array will change
	c:=a[1:3]
	fmt.Println(a)//[5 66 77 88 99]
	fmt.Println(c)//[66 77]
	a[1]=10000
	fmt.Println(a)//[5 10000 77 88 99]
	fmt.Println(c,b)//[10000 77] [5 10000 77 88]
	
}

The slice can also be cut again.

It should be noted that if the slicing operation exceeds the upper limit of cap(), a panic exception will be caused, but exceeding len() means that slice is extended because the length of the new slice will become larger.

func slice() {
	months	:=	[...]string{1:	"January",2:"February",3:"March",4:"April",5:"May",6:"June",7:"July",8:"August",9:"September",10:"October",11:"November",	12:	"December"}
	summer:=months[6:9]
	fmt.Println(summer[:20])	//	panic:	out	of	range
	endlessSummer	:=	summer[:5]	//	extend	a	slice	(within	capacity)
	fmt.Println(endlessSummer)//[June July August September October]
}

A slice with a zero value is equal to nil. Slice with a nil value has no underlying array. The length and capacity of slice with a nil value are both
Is 0, but the length and capacity of slice with non nil value are also 0, such as [] int {} or make([]int,3)[3:]

Comparison of slices

Slices cannot be directly compared because they are of reference type and can be compared with nil. It can be understood that a slice with nil value has no underlying array. The length and capacity of a slice with nil value are 0, but the slices with both length and capacity are not necessarily nil.

	s1:=make([]int,0)
	s2:=[]int{}

	fmt.Printf("s1=%v,len=%d,cap=%d\n",s1,len(s1),cap(s1))
	fmt.Printf("s2=%v,len=%d,cap=%d\n",s2,len(s2),cap(s2))
	fmt.Println(s1==nil)
	fmt.Println(s2==nil)
	/*
	s1=[],len=0,cap=0
	s2=[],len=0,cap=0
	false
	false
	 */

Therefore, if you want to judge whether a slice is empty, you can't use slice==nil, but len(s)==0.

Assigned copy of slice

The two variables before and after copying share the underlying array. The modification of one slice will affect the content of the other slice. In other words, if either slice or original array is changed, the other will be affected and changed accordingly.

func copySlice() {
	s := []int{1, 3, 5, 7, 9}
	s2 := s
	//Modify the value of the slice
	for i := 0; i < len(s2); i++ {
		s2[i] = 0
	}
	fmt.Println(s2) //[0 0 0 0 0]
	fmt.Println(s)  //[0] the value of the original array will also be changed

}

The traversal of slices is the same as that of arrays, using for i/range

Expansion of slices

  • First, judge that if the newly applied capacity is greater than twice the original capacity, the final capacity is the newly applied capacity.
  • If the length of the old slice is less than 1024, the final capacity is twice as large as before
  • If the length of the old slice is greater than or equal to 1024, the final capacity is increased to 1 / 4 of the original
  • If the final capacity overflows, the final capacity is the newly applied capacity

append helps slices allocate memory for initialization

Pointer

The pointer saves the memory address of the variable. Through the & variable name, * variable indicates that the value in the memory address pointed to by the pointer is retrieved

new and make

  • Both are used to request memory
  • new is generally used for basic data type applications, such as string, int, and return type pointers (memory addresses)
  • make is used to apply for memory for slice, map and chain, and return the corresponding type itself

map

All keys in the map have the same type, and all values have the same type, but key and value can have different data types. The key must support the = = comparison operator, so the map can judge whether it already exists by testing whether the keys are equal.

map is a data structure of key value pair type. map in Go language is a reference type and must be initialized before use.

For map type variables, the initial value is nil. You need to provide make to apply for memory. cap is the initial capacity

If you access a key that does not exist in the map, the corresponding zero value will be returned.

Delete the specified key in the map through delete. If the key does not exist, do nothing.

Before using map, slice and chain, you must first apply for memory space through make.

	//The declaration of map. Before using a map, you must first apply for memory and then add data, otherwise an error will be reported
	m1:=make(map[string]int,10)

	//map settings
	m1["zhangsan"]=80
	m1["lisi"]=70
	m1["tom"]=90
	fmt.Println(m1)
	//map determines whether the key exists
	score,ok:=m1["jerry"]
	if !ok{
		fmt.Println("key not exit!")
	}else{
		fmt.Println(score)
	}
	//map delete value
	delete(m1,"tom")
	fmt.Println(m1)

All these operations are safe and will not report an error even if they do not exist. For nonexistent key s, the map returns a zero value of this value type. However, the element in the map is not a variable, so we can't take the address of the element in the map. In addition, with the increase of the number of elements, more memory space needs to be allocated, which may invalidate the previous address.

The map can be traversed through the for loop. The map is unordered, and the order of each traversal may be different.

func main() {
   ages:=map[string]int{
      "xifeng":19,
      "tom":22,
      "bing":24,
   }
   for    name,  age    := range  ages   {
      fmt.Printf("%s\t%d\n", name,  age)
   }
}

If you want to output the map in order, you can sort the key s first, and then get them from the map in turn.

structural morphology

A structure is an aggregated data type. It is an entity formed by aggregating zero or more values of any type. Each value is called a member of the structure. Members of the structure can be accessed and assigned through the point operator. You can also access member variables through pointers.

The member input order of structure is of great significance. If the exchange order is different, it is different structure types. Related members are usually written together.

If a member's name starts with a capital letter, the member is exported. This is the Go language export rule.

If efficiency is considered, larger structures are usually passed in and returned in the form of pointers. If you want to modify the structure member variable, it is necessary to pass it in with a pointer.

Comparison of structures

If all members of the structure can be compared, the structure can also be compared. In that case, the two structures can use = = or= Compare.

type	Point	struct{	X,	Y	int	} 
p	:=	Point{1,	2}
q	:=	Point{2,	1}
fmt.Println(p.X	==	q.X	&&	p.Y	==	q.Y)	//	"false" 
fmt.Println(p	==	q)			//false

Anonymous member

The Go language has a feature that allows us to declare only the data type corresponding to a member without naming the member's name; Such members are
Anonymous members. The data type of an anonymous member must be a named type or a pointer to a named type. Circle and Wheel each have an anonymous member. We can say that the Point type is embedded in the circle structure
At the same time, the Circle type is embedded in the Wheel structure.

type Circle	struct	{ 
			Point
			Radius	int 
}
type Wheel	struct	{ 
			Circle
			Spokes	int 
}

Equivalent to the following

package main

import "fmt"

type Employee struct {
	ID        int
	Name      string
	Address   string
	Postition string
	Salary    int
	ManagerID int
}

var dilbert Employee

type Point struct {
	X int
	Y int
}

type	Circle	struct	{
	Point
	Radius	int
}
type	Wheel	struct	{
	Circle
	Spokes	int
}

func main() {
	dilbert.Address = "beijing"
	dilbert.ID = 10086
	dilbert.Name = "jerry"
	dilbert.Postition = "backend"
	dilbert.Salary = 1000
	dilbert.ManagerID = 10000
	//It should be noted that the% v parameter in the Printf function contains an # adverb, which indicates that the value is printed in a syntax similar to the Go language. about
	//For a structure type, it will contain the name of each member.
    //main.Employee{ID:10086, Name:"jerry", Address:"beijing", Postition:"backend", Salary:1000, ManagerID:10000}
	fmt.Printf("%#v\n",dilbert)
	var w    = Wheel{
		Circle:    Circle{
			Point:        Point{X:    8, Y:    8},
			Radius:    5,
		},
		Spokes:    20, //	NOTE:	trailing	comma	necessary	here	(and	at	Radius)
	}
	fmt.Printf("%#v\n", w)
    
//main.Wheel{Circle:main.Circle{Point:main.Point{X:8, Y:8}, Radius:5}, Spokes:20}
}

defer

A defer modified function or statement will be executed after the return value assignment operation. When there are multiple defer modified statements or functions, they will be placed on the stack, declared first and executed last.

In the function of Go language, the return statement is not an atomic operation at the bottom. It will be divided into two steps: return value assignment and RET instruction. The execution time of defer statement is after the return value assignment and before the RET instruction.

Search for function internal variables: first find the variables used in the function. If there are no local variables, find them in the global; Local variables can only be used inside functions.

Due to the delayed call of defer statement, defer statement can easily deal with the problem of resource release, such as resource cleaning, file closing, unlocking and recording time.

function

The formal parameter of a function is a copy of the argument, and only the value is passed in go. Modifying the formal parameter will not affect the argument, but if the argument contains reference types, such as pointer, slice, map, function, channel, etc., the argument will be modified due to the introduction reference of the function.

In the go language, a function can return multiple values.

func findLinks(url string) ([]string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, fmt.Errorf("getting	%s:	%s", url, resp.Status)
	}
	doc, err := html.Parse(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, fmt.Errorf("parsing	%s	as	HTML:	%v", url, err)
	}
	return visit(nil, doc), nil
}

In Go, functions are regarded as the first type of values: functions, like other values, have types, can be assigned to other variables, passed to functions, and returned from functions. A call to a function value is no different from a function.

func power(n int) int {
	return n*n
}

func main() {
	f:=power
	fmt.Println(f(3))
	fmt.Printf("%T\n",f)// func(int) int
}

The zero value of function type is nil, and calling the function value of nil will cause panic error.

Functions are not comparable, and function values cannot be used as the key s of map s.

Anonymous function

Function value literal is an expression. Its value is called an anonymous function. When using a function, it is defined.

func squares() func()int {
	var x int
	return func() int {
		x++
		return x*x
	}
}

func main() {
	sq:=squares()
	fmt.Println(sq())//1
	fmt.Println(sq())//4
	fmt.Println(sq())//9
}

Anonymous internal functions defined in squares can access and update local variables in squares, which means that there are variable references in anonymous functions and squares. This is why function values belong to reference types and function values are not comparable. Go uses closures to implement
Now function values, Go programmers also call function values closures.

The example of anonymous function shows that the life cycle of a variable is not determined by its scope.

Built in function

  • Close: mainly used to close the channel
  • len: used to find length, string, array, slice, map, channel
  • new: used to allocate memory. It is mainly used to allocate value types, int and struct. Pointer returned.
  • make: used to allocate memory. It is mainly used to allocate references, such as chan,map,slice
  • Append: used to append elements to the array, slice
  • panic/recover: used for error handling

At present, the Go language has no exception mechanism, but it uses the panic/recover mode to handle errors. Panic can be thrown anywhere, but recover is only valid in the function called by defer.

be careful:

  • recover() must be used with defer
  • The defer must be defined before the statement that may cause panic

error

The built-in error is the interface type. The error may be nil or non nil. Nil means that the function runs successfully, otherwise it fails. In go, when the function fails, it will return error messages. These error messages are considered as an expected value rather than an exception. This is the difference between go and java in exception mechanisms, but these mechanisms are only used to deal with unexpected errors, that is, bug s.

Processing strategy

  1. Propagation error. Once a failure occurs, it represents the failure of the function. Returns the error message to the caller
  2. If the error is accidental or unpredictable, try again and fail.
  3. If the program cannot continue to run after the error occurs, you can output the error message and end the program. This strategy is only carried out in main.
  4. Output error information through log without stopping the program.
  5. Ignore errors directly.

Interface

Many object-oriented languages have similar interface concepts, but the uniqueness of interface type in Go language is that it meets implicit implementation. That is, it is not necessary to define all the interface types satisfied for a given specific strength.

Interface agreement

Interface type is an abstract rationality. It will not expose the structure of the internal value of the object and the collection of basic operations supported by the object. They will only show their own methods, that is, you don't know what it is, but you know what it does.

Interface type

An interface type specifically describes a collection of methods. A specific type that implements these methods is an instance of this interface type.

type Reader interface {
	Read(p []byte) (n int, err error)
}
type Writer interface {
	Write(p []byte) (n int, err error)
}


//Composite interface
type ReadWriter interface {
	Reader
	Writer
}

Conditions for implementing the interface

If a type has all the methods required by an interface, the type implements the interface. Go programmers often briefly describe a specific type as a specific interface type. For example, * bytes Buffer is Io Writer´╝Ť It's io ReadWriter.

The empty interface type has no requirements for implementing its type, so we can assign any value to the empty interface type.

var any interface{} 
any = true
any = 12.34
any = map[string]int{"one":1}
fmt.Println(any)

Each specific type group can be expressed as an interface type based on their same behavior. Unlike class based language, the interface set implemented by a class needs to be displayed and defined. In Go language, we can define a new abstract or specific feature group when necessary without modifying the definition of specific types.

Interface value

Interface values are called dynamic types and dynamic values of interfaces. A specific type and the value of that type. go is a statically typed language, and type is the concept of compile time.

In Go language, variables are always initialized by a well-defined value, even the interface is no exception. The zero value of an interface is that its type and value part are nil.

The above figure is an empty interface value. You can use w==nil or W= Nil to determine whether the interface value is empty. Calling any method on an empty interface value will produce a panic.

The two interface values are equal only if they are both nil values or their dynamic types are the same, and the dynamic values are also equal according to the = = operation of this dynamic type. Therefore, the interface values are comparable. If two interface values have the same type but this type is not comparable (e.g. slice, function), the comparison will fail and a panic will be thrown.

var	x interface{}	=	[]int{1,	2,	3}
fmt.Println(x	==	x)	//	panic:	comparing	uncomparable	type	[]int

A nil interface that does not contain any value and an interface that contains a nil pointer have different values.

Expression evaluation

Build a simple arithmetic expression evaluator.

package main

import (
	"fmt"
	"math"
)

type Var string
type literal float64
type Env map[Var]float64
type unary struct {
	op rune
	x  Expr
}

type binary struct {
	op   rune
	x, y Expr
}
type call struct {
	fn   string
	args [] Expr
}

type Expr interface {
	Eval(env Env) float64
}

func (v Var) Eval(env Env) float64 {
	return env[v]
}

func (l literal) Eval(_ Env) float64 {
	return float64(l)
}

func (u unary) Eval(env Env) float64 {
	switch u.op {
	case '-':
		return -u.x.Eval(env)
	case '+':
		return +u.x.Eval(env)
	}
	panic(fmt.Sprintf("unsupported unoperator:%q", u.op))
}

func (b binary) Eval(env Env) float64 {
	switch b.op {
	case '+':
		return b.x.Eval(env) + b.y.Eval(env)
	case '-':
		return b.x.Eval(env) - b.y.Eval(env)
	case '*':
		return b.x.Eval(env) * b.y.Eval(env)
	case '/':
		return b.x.Eval(env) / b.y.Eval(env)
	}
	panic(fmt.Sprintf("unsupported unoperator:%q", u.op))
}

func (c call) Eval(env Env) float64 {
	switch c.fn {
	case "pow":
		return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env))
	case "sin":
		return math.Sin(c.args[0].Eval(env))
	case "sqrt":
		return math.Sqrt(c.args[0].Eval(env))
	}
	panic(fmt.Sprintf("unsupported	function	call:	%s", c.fn))
}

Type Asserts

A type assertion is an operation used on an interface value. x.(T) is called an assertion type, where x represents an interface type and T represents a type. A type assertion checks whether the dynamic type of its operand matches the asserted type.

First, if the asserted type T is a specific type, then the type assertion checks whether the dynamic type of x is the same as T. If the judgment fails, panic will be thrown.

	var w io.Writer
	w= os.Stdout
	f:=w.(*os.File)
	fmt.Println(f)
	c:=w.(*bytes.Buffer)// panic interface conversion: io.Writer is *os.File, not *bytes.Buffer
	fmt.Println(c)

Second, the type assertion of an interface type changes the expression of the type and the set of methods that can be obtained, but it protects the dynamic types and values within the interface value.

var	w	io.Writer 
w	=	os.Stdout
rw	:=	w.(io.ReadWriter)	//	success:	*os.File	has	both	Read	and	Write 
w	=	new(ByteCounter)
rw	=	w.(io.ReadWriter)	//	panic:	*ByteCounter	has	no	Read	method

If the interface of the assertion is a nil, the assertion of this type will fail regardless of the assertion type. We hardly need to assert a less restrictive interface type.

Use specialized types to describe structured errors. The os package defines a pathError type to describe the failures involved in file path operations, and defines a variant of LinkError to describe operations involving two file paths.

import (
   "errors"
   "fmt"
   "os"
   "syscall"
)

// Identifying error types based on type assertions
var ErrorNotExist = errors.New("file not exist")

func IsNotExist(err error) bool {
   if pe,ok:=err.(*os.PathError);ok{
      err =pe.Err
   }
   return err==syscall.ENOENT||err==ErrorNotExist
}

func main() {
   _,err:=os.Open("/no/such/file")
   fmt.Println(os.IsNotExist(err))//true
}

Concurrent programming

Goroutines

In go language, each concurrent execution unit is called a goroutines, which can be understood by analogy with threads in other programming languages. When a program starts, the main function runs in a separate goroutines, which we call main goroutine. The new goroutine will be created with the go statement.

When the main function returns, all goroutines will be interrupted and the program will exit. In addition to sub function exit and program termination, there is also a way to terminate execution by communicating between goroutines.

func fib2(x int64) int64 {
   if x < 2 {
      return x
   }
   return fib2(x-1)+fib2(x-2)
}
func spinner(delay time.Duration) {
   for    {
      for    _, r  := range  `-\|/` {
         fmt.Printf("\r%c", r)
         time.Sleep(delay)
      }
   }
}
func main() {
   go spinner(100*time.Millisecond)
   n:=fib2(45)
   fmt.Println(n)
}

runtime.GoExit(): terminates the collaboration.

func test() {
   defer fmt.Println("ffffffffff")
   runtime.Goexit()
   fmt.Println("eeeeeeeeeeeeeee")
   //Termination process
}
func main() {
   go func() {
      fmt.Println("aaaaaaaaaaa")
      test()
      fmt.Println("bbbbbbbbbbbbbbbbb")
   }()
   for i := 1; i > 0; i=1{

   }
}

Output results:

aaaaaaaaaaa
ffffffffff

runtime.GOMAXPROCS(): sets the maximum number of CPU cores and returns the previously set value.

channel type

For the reference created by make, the zero value of channel is also nil. When the method parameter type is channel, the caller and callee will reference the same channel object.

func main() {
   // channel creation
   ch:=make(chan string)
   defer fmt.Println("main Coroutine call completed...")
   go func() {
      defer fmt.Println("Subroutine call completed...")
      for i := 0; i < 2; i++ {
         fmt.Println("Subprocess:",i)
         time.Sleep(time.Second)
      }
      //input data
      ch<-"game over"

   }()
   //Take data out of the pipe
   str:=<-ch
   fmt.Println(str)
}

Unbuffered channel refers to a channel that does not have the ability to save any value before receiving. This type of channel requires both sending goroutines and receiving goroutines to be prepared at the same time in order to complete the sending and receiving operations. If two goroutines are not prepared at the same time, the channel will cause the goroutines that perform the sending or receiving operation to block and wait.

The sending and receiving behaviors of the channel are synchronous, and any operation cannot exist alone.

When a buffered channel is re created, the capacity is passed in. He won't read until the buffer is full.

ch	=	make(chan	string,	3)// The buffer size is 3
fmt.Println(cap(ch))	//	"3"
ch	<-	"A" 
ch	<-	"B"
fmt.Println(len(ch))	//	"2" returns the number of valid elements in the channel internal cache queue

The sending operation to the cache channel is to insert elements into the tail of the internal cache queue, and the receiving operation is to delete elements from the head of the queue. If the internal cache queue is full, the send operation will be blocked until a new queue space is released due to another goroutine performing the receive operation. Conversely, if the channel is empty, the receive operation will block until another goroutine performs the send operation and inserts an element into the queue.

Single channel producer consumer

package main

import (
	"fmt"
)

func producer(out chan<- int) {
	//out reference passing
	for i := 0; i < 10; i++ {
		out <- i * i
	}
	close(out)
}
func consumer(in <-chan int) {
	//Take out data output from channel
	for e := range in {
		fmt.Println(e)
	}

}

func main() {
	ch := make(chan int)
	go producer(ch)
	consumer(ch)
}

Timer

Timer is a timer that represents a single event in the future. You can tell the timer how long to wait. It provides a channel and enters a time value into the channel after a fixed time in the future. Timer can only be executed once.

func main() {
	//Create a timer. After 2 seconds, the timer will send a current timestamp to its C channel
	timer := time.NewTimer(2 * time.Second)
	fmt.Println("start Time:",time.Now())
	t1:=timer.C
	fmt.Printf("t1 Time::%v\n",<-t1)

	//Just wait and do nothing
	timer2:=time.NewTimer(2*time.Second)
	<-timer2.C
	fmt.Println("2s after",time.Now())

}

Result output

start time: 2022-01-02 11:24:37.9578473 +0800 CST m=+0.003826801
t1 time: 2022-01-02 11:24:39.9707637 +0800 CST m=+2.016743201
2s later 2022-01-02 11:24:41.9723786 +0800 CST m=+4.018358101

Three methods of delay program

func delay() {
   // The first direct sleep fixed time
   time.Sleep(2*time.Second)
   // The second is through timer
   <-time.NewTimer(2*time.Second).C
   // The third is through timer After
   <-time.After(2*time.Second)
}

Stop timer: time Stop()

Reset timer time: time Reset()

Ticker

Ticker is a timed trigger timer that periodically executes tasks, so the receiver of channel can read data from it within a specified time.

func main() {
   ticker := time.NewTicker(time.Second)
   for i := 0; i < 100; i ++ {
      //Output every 1s
      <-ticker.C
      fmt.Println(i)
   }

}

select

select in Go can monitor the data flow on the channel.

The usage of select is very similar to that of switch language. A new selection block starts with select, and each selection condition is described by a case statement.

Each case is an IO operation for the channel.

func fibonacci(ch chan<-int,quit <-chan bool)  {
	x,y:=1,1
	for {
		select {
		//x write channel
		case ch<-x:
			x,y=y,x+y
		case flag:=<-quit:
			fmt.Println(flag)
			return
		}
	}
}

func main() {
	ch:=make(chan int)
	quit:=make(chan bool)
	go func() {
		// Output 8 Fibonacci results
		for i := 0; i < 8; i++ {
			//When ch there is data written, the data is taken out and output
			num:=<-ch
			fmt.Println(num)
		}
		quit<-true
	}()
	//The main coroutine starts by entering x into ch
	fibonacci(ch,quit)
}

Topics: Go