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:
- Every statement outside a function must start with a keyword (var, const, func, etc.)
- : = cannot be used outside a function.
- _ Mostly used for placeholders, indicating that the value is ignored.
- 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
uint8 | Unsigned 8-bit integer (0 to 255) |
---|---|
uint16 | Unsigned 16 bit integer (0 to 65535) |
uint32 | Unsigned 32-bit integer (0 to 4294967295) |
uint64 | Unsigned 64 bit integer (0 to 18446744073709551615) |
int8 | Signed 8-bit integer (- 128 to 127) |
int16 | Signed 16 bit integer (- 32768 to 32767) |
int32 | Signed 32-bit integer (- 2147483648 to 2147483647) |
int64 | Signed 64 bit integer (- 9223372036854775808 to 9223372036854775807) |
Special integer
type | describe |
---|---|
uint | uint32 on a 32-bit operating system and uint64 on a 64 bit operating system |
int | It is int32 on 32-bit operating system and int64 on 64 bit operating system |
uintptr | Unsigned 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
- Propagation error. Once a failure occurs, it represents the failure of the function. Returns the error message to the caller
- If the error is accidental or unpredictable, try again and fail.
- 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.
- Output error information through log without stopping the program.
- 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) }