1.Go overview
A program is an ordered combination of computer instructions. Program = algorithm + data structure. Any program can be implemented by combining modules through three basic control structures (sequence, branch, loop).
Go (also known as Golang) is an open source programming language developed by Google. The design goal is to make programming easier, more efficient and more reliable. Go aims to provide high performance, concise and easy-to-understand syntax. It combines the speed and performance of traditional compiled languages with the ease of use and convenience of dynamically typed languages.
Go language features:
1. Static compilation
2. Less is more, the syntax is concise and simple, and it is highly readable.
3. Native support for concurrent programming
4. Non-intrusive interface to Duck model
5. Emphasize combination, which is better than inheritance
6. Supports cross-compilation of multiple operating systems and architectures. HeretargetOS
is the target operating system (such as windows, linux, darwin, etc.), targetArchitecture
is the target architecture (such as amd64, arm, 386, etc.).
GOOS=targetOS GOARCH=targetArchitecture go build
GOOS=windows GOARCH=amd64 go build
7. Make extensive use of interfaces and built-in functions to improve code reuse
8. Supports the mutual calling mechanism (CGO) with C language. Go uses the C
package to call C functions, and uses special types to handle the communication between Go and C. The data passed. Here's a simple example showing how to call a C function in Go:
#include <stdio.h>
void helloFromC() {
printf("Hello from C!\n");
}
package main
/*
#cgo CFLAGS: -g -Wall
void helloFromC();
*/
import "C"
func main() {
C.helloFromC()
}
9. Precise dependencies and shorten compilation time through incremental compilation, parallel compilation and caching compilation results
Basic commands of Go language:
2.Go basic syntax
There are 25 keywords in Go language, which is a statically strongly typed language.
Strong type: The compiler will confirm the type of each variable, and incorrect use will cause an error;
static: only supports automatic type inference at compile time
For conditional statements in Go language, () is not required after the if judgment. At the same time, if can be used with an initialization substatement; it is separated from the condition. At the same time, Go does not support the ternary operator
/*
if SimpleStmt;Expression {
statement
......
}
*/
if i:=10;i>8 {
//条件语句
}
Switch statement description:
Note: The math.Floor(num) function is used to return the smallest integer less than num
At the same time, switch is a lazy evaluation, and the expression is calculated only when evaluation is needed, thereby reducing consumption and improving performance.
for loop:
1. The loop statement of GO only has for, not while/do while
2. There is no need to add ( ) after the for statement
3.for When omitting any of the three parts of the statement, the semicolon cannot be omitted
4. When only conditional judgment is left, the semicolon is not needed (equivalent to the while statement)
5. Omit everything and it becomes an infinite loop
//while:
for experssion {
}
//无限循环
for {
if state {
break
}
}
goto can work more closely with labels and can replace break to jump out of multiple loops
Manual sorting
Handwritten implementation of bubble sort, the Go code is as follows:
It should be noted here that passing slices as parameters is passed by reference!
func bubbleSort(nums []int){
n:=len(nums)
// 这里i是定义排序好的数量
for i:=0;i<n-1;i++ {
// 每次排序都是从第一个元素开始冒泡
for j:=0;j<n-1-i;j++ {
if nums[j]>nums[j+1] {
nums[j+1],nums[j]=nums[j],nums[j+1]
}
}
}
}
To implement insertion sort by hand, the Go code is as follows:
func insertSort(nums []int){
n:=len(nums)
// 从无序组第二个元素开始依次插入有序组中
for i:=1;i<n;i++{
key:=nums[i]
j:=i-1
for j>=0 && key<nums[j]{
nums[j+1]=nums[j]
j--
}
nums[j+1]=key
}
}
Manually implement quick sorting, the specific Go code is as follows
func quickSort(nums []int){
n:=len(nums)
if n<2 {
return
}
// 定义基准线
pivot:=nums[0]
low,high:=0,n-1
for low<=high {
if nums[low]<=pivot{
low++
}else{
nums[low],nums[high]=nums[high],nums[low]
high--
}
}
// 交换基准元素位置
nums[0],nums[high]=nums[high],nums[0]
// 递归排序左右子数组
quickSort(nums[:high])
quickSort(nums[high+1:])
}
3.Basic data types and operations
The basic data types in Go language are: integer, floating point, complex number, Boolean, character, string and error type.
You can use the reflect.TypeOf function to view the type name
1.Basic type
1. Integer type
Integers can be divided into:signed bits and unsigned bits;
Integers can be divided according to the number of digits: int int8 int 16 int32 int32 int64
Note here:Different integer types cannot be directly compared or operated directly
2. Floating point numbers
Floating point numbers mainly include float32 and float64
All functions in the standard library math package use float64
3.Plural
Complex numbers are represented by two floating point numbers, a real part and an imaginary part.
There are two complex number types, complex64 (composed of two float32) and complex128 (composed of two float64)
There are three built-in complex number handling functions
complex(float,float) creates a complex number
real() gets the real part
image() gets the imaginary part
package main
import (
"fmt"
)
func main() {
// 创建复数
var comp1 complex64 = complex(2, 3) // 实部为2,虚部为3
comp2 := complex(4.5, 7.1) // 使用默认类型complex128
// 输出复数
fmt.Println("Complex 1:", comp1)
fmt.Println("Complex 2:", comp2)
// 访问实部和虚部
fmt.Println("Real part of Complex 1:", real(comp1)) // 输出实部
fmt.Println("Imaginary part of Complex 1:", imag(comp1)) // 输出虚部
}
4. Boolean type
Boolean values mainly include true and false, and the type length is 1byte
Boolean types cannot be assigned to other types and do not support type conversion.
The Boolean type here does not support using 0 and 1 to represent true and false.
The conditional part of if and for statements must be a boolean value or expression
2.Operator
Operators mainly include arithmetic operators, relational operators, logical operators, assignment operators and bitwise operators
1. Arithmetic operators
Arithmetic operators mainly include addition, subtraction, multiplication and division, modulo, auto-increment, and auto-decrement.
**Note: **Go language only supports variable ++ for auto-increment and does not support ++ variables. The same goes for auto-decrement.
2. Relational operators
Relational operators mainly include
==
!=
>
<
>=
<=
Note: Since the Boolean type does not support conversion to integer types, the syntax of continuous inequalities, such as x<y<z, is wrong!
3. Logical operators
Logical operators mainly include negation! , and &&, or ||
4. Assignment operator
5. Bit operators
Bit operators include
<< 左移 相当于乘以2
>> 右移 相当于除以2
& 位与
| 位或
^ 异或
Bitwise operations only work on integers, which is a low-level operation and has high efficiency!
4. Collection data type
There are three main types of collections in Go language, namely Array, Slice and Map.
1.Array
An array is a collection of similar elements. After an array variable is declared, its element type and array length are immutable.
Array declaration:
// 只声明未赋值H
var arr1 [5]int
// 直接赋值
arr2:=[3]int{
1,2,3}
// 数组长度由初始化数量确定
arr3:=[...]int{
1,2,3} //...不可省略
// 对含有下标的元素赋初值 其余元素保持零值
arr4:=[4]{
0:99,3:100}
Array copy:
Copying between array variables will copy the entire array (value copy)
a := [...]string{
"USA", "China", "India", "Germany"}
b := a
b[0] = "Singapore"
fmt.Println("a is ", a)
fmt.Println("b is ", b)
//a is [USA China India Germany]
//b is [Singapore China India Germany]
Array parameter passing:
With the array copy type, only the actual parameters are copied to the formal parameters, and they are destroyed when the function call is completed. The two are independent of each other, and the efficiency is low when passing large arrays!
func changeLocal(num [5]int) {
num[0] = 55
fmt.Println("inside function ", num)
}
func main() {
num := [...]int{
5, 6, 7, 8, 8}
fmt.Println("before passing to function ", num)
changeLocal(num) //num is passed by value
fmt.Println("after passing to function ", num)
}
//before passing to function [5 6 7 8 8]
//inside function [55 6 7 8 8]
//after passing to function [5 6 7 8 8]
Array traversal:
Array traversal can use for loop traversal or range traversal
// for循环
a := [...]float64{
67.7, 89.8, 21, 78}
for i := 0; i < len(a); i++ {
fmt.Printf("%d th element of a is %.2f\n", i, a[i])
}
// range遍历
a := [...]float64{
67.7, 89.8, 21, 78}
for i, v := range a {
// 第一个参数为序号,第二个为变量
fmt.Printf("%d the element of a is %.2f\n", i, v)
}
//0 the element of a is 67.70
//1 the element of a is 89.80
//2 the element of a is 21.00
//3 the element of a is 78.00
In addition, if only one of the two parameters of range traversal is used, an error will be reported. You can use _ placeholder to indicate that only one parameter is used.
Multidimensional Arrays:
a := [3][2]string{
{
"lion", "tiger"},
{
"cat", "dog"},
{
"pigeon", "peacock"}, //此处,不可忽略,否则报错
}
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
2. Slice
Since the fixed-length nature of arrays and value copying limit their use, slicing is provided, that is, array references with variable lengths are provided.
The size of the underlying array cannot be given when declaring a slice, otherwise it becomes an array declaration. At the same time, you can use the built-in function make to declare and initialize!
Slices are reference types and do not support the == operation
Create slices:
It should be noted here that if you specify the underlying array when creating a slice, once the slice changes, the underlying array elements will also change, because the slice is a reference to the original array. Therefore, multiple slices can share the same underlying array.
// 指定底层数组创建
a := [5]int{
76, 77, 78, 79, 80}//底层数组
s1 := a[0:4] // from a[0] to a[3]
s2 := a[:4] // from a[0] to a[3]
s3 := a[2:5] // from a[2] to a[4]
s4 := a[2:] // from a[2] to a[4]
fmt.Printf("%v\n%v\n%v\n%v", s1, s2, s3, s4)
//[76 77 78 79]
//[76 77 78 79]
//[78 79 80]
//[78 79 80]
// 同时创建数组和切片
//指定数组大小,只创建数组
c := [3]int{
6, 7, 8}
//不指定数组大小,返回切片引用,底层数组匿名
d := []int{
6, 7, 8}
//用...推断数组大小,只创建数组
e := [...]int{
6, 7, 8}
The built-in function len() returns the current length of the slice
The built-in function cap() returns the capacity of the underlying array of the slice
Slices are dynamically added:
The built-in function append() dynamically expands the slice and directly overwrites the underlying array elements within the capacity of the underlying array.
package main
import "fmt"
func main() {
arr := [7]int{
9, 8, 7, 6, 5, 4, 3}
sli := arr[1:3]
sli = append(sli, 20) // 增加一个20,切片容量扩展一倍
fmt.Printf("%v\n", arr) //[9 8 7 20 5 4 3]
fmt.Printf("%v\n", sli) //[8 7 20]
}
When the slice is dynamically increased, when the capacity of the underlying array is exceeded, the underlying array will be re-created and the data will be transferred
The slice will grow exponentially when the elements are less than 1000 and exceed 1000. , the growth rate is about 1.25
cars := []string{
"Ferrari", "Honda", "Ford"}
fmt.Println("cars:", cars, "length", len(cars), "capacity", cap(cars))
//cars: [Ferrari Honda Ford] length 3 capacity 3
fmt.Printf("%x\n", &cars[0])
//c000080330
cars = append(cars, "Toyota")
fmt.Println("cars:", cars, "length", len(cars), "capacity", cap(cars))
//cars: [Ferrari Honda Ford Toyota] length 4 capacity 6 //why 6
fmt.Printf("%x\n", &cars[0])
//c0000a4000 //Why
Slice merging:
The built-in function append() also supports the merging of slices. Use the... operator to take out all elements of the corresponding slice.
veggies := []string{
"potatoes", "tomatoes", "brinjal"}
fruits := []string{
"oranges", "apples"}
food := append(veggies, fruits...) //... 不可忽略
fmt.Println("food:", food)
//food: [potatoes tomatoes brinjal oranges apples]
Slice passing parameters:
When passing parameters to a function, what is copied is a copy of the structure, realizing transfer by reference.
func subtactOne(numbers []int) {
for i := range numbers {
numbers[i] -= 2
}
}
func main() {
nos := []int{
8, 7, 6}
fmt.Println("slice before function call", nos)
//slice before function call [8 7 6]
subtactOne(nos)
fmt.Println("slice after function call", nos)
//slice after function call [6 5 4]
}
Multidimensional slices:
Multidimensional slices are more flexible than multidimensional arrays, and the number of elements in each row does not have to be the same
pls := [][]string{
{
"C", "C++", "C#"},
{
"JavaScript"},
{
"Go", "Rust"},
}
for _, v1 := range pls {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
//C C++ C#
//JavaScript
//Go Rust
3.Map
Map is used to store a series of unordered key-value pairs. It is a reference type and does not support == operations (except nil).
Map is not thread-safe and does not support concurrent writing
1.Initialization
Map zero value is not available, it only declares that it will not be initialized to nil value, no underlying storage space is allocated, and elements cannot be added
Elements can be added after initialization with literals or make function< /span>
var m1 map[string]int
fmt.Println(m1 == nil)
//true
//m1["a"] = 1 //error
m2 := map[string]int{
}
fmt.Println(m2 == nil)
//false
m2["a"] = 1 //ok
m3 := make(map[string]int)
fmt.Println(m3 == nil)
//false
m3["a"] = 1 //ok
2. Assignment
Element assignment can be performed after Map initialization, or element assignment can be performed directly during Map initialization.
personSalary := make(map[string]int)
personSalary["steve"] = 12000
personSalary["jamie"] = 15000
personSalary["mike"] = 9000
//初始化时,直接赋值
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
3. Element search
Map elements can actually return two values when accessed through subscripts (the bottom layer is actually a function, Comma-ok method)
1. The corresponding value
2. Boolean value indicating whether the corresponding key exists
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
value, ok := personSalary["joe"]
if ok == true {
fmt.Println("Salary of joe is", value)
} else {
fmt.Println("joe not found")
}
4.Map element traversal
Map elements can be traversed using range, butthe order is not guaranteed
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
for key, value := range personSalary {
fmt.Printf("personSalary[%s] = %d\n", key, value)
}
//personSalary[mike] = 9000
//personSalary[steve] = 12000
// personSalary[jamie] = 15000
5.Map element deletion
Use the built-in function delete() to delete Map elements
1. The key exists and the corresponding element is deleted
2. The key does not exist and there is nothing happen
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("map before deletion", personSalary)
delete(personSalary, "steve")
fmt.Println("map after deletion", personSalary)
//map before deletion map[jamie:15000 mike:9000 steve:12000]
//map after deletion map[jamie:15000 mike:9000]
5.Go function
High function efficiency means high program efficiency. It is recommended to use more standard library functions.
1. Function definition
//语法格式
func funcName(paramList)(resultList) {
coding ……
}
//paramList = input1 type1,input2 type2 ……
//resultList = output1 type1,output2 type2 ……
//多个相邻相同类型参数可以使用简写
func add(a, b int) int {
return a + b
}
There can be multiple return values in a Go function, and the return values can have variable names and be visible in the function body.
**NOTE:** Function overloading is not supported as overloading is only occasionally useful but in practice leads to unsolvable problems and brittleness
2. Parameters (indefinite parameters)
Indefinite parameters, formal parameters are variable and uncertain
Indefinite parameter declaration syntax format: param … type
The formal parameters of indefinite parameters are slices within the function
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
The above-mentioned variable parameters have an indefinite number, but the types are the same. If you want to implement an indefinite number and an indefinite type at the same time, you need to implement it through the interface type interface{} as a parameter.
func printAll(vals ...interface{
}) {
for _, val := range vals {
fmt.Println(val)
}
}
3.Anonymous functions
Anonymous functions are equivalent to function literals. Anonymous functions can be used wherever functions can be used.
//匿名函数直接调用
func(a,b int )int{
return a-b
}(5,4)
//匿名函数赋值给函数变量
var sum = func(a,b int )int{
return a+b
}
//函数作为返回值
func getFun(op string) func(a,b int )int {
return func(a,b int )int{
return a+b
}
}
4. Closure
Closure = function + reference environment, it is common to define an anonymous function inside a function, and the anonymous function accesses the scope of the external function that defines it
package main
import "fmt"
func main() {
// 外部函数外的变量
outsideVar := 10
// 内部函数,形成闭包
closureFunc := func() {
fmt.Println(outsideVar) // 闭包函数内部访问外部变量
}
closureFunc() // 调用闭包函数
}
Function currying:
Function currying is to transform a function that receives multiple parameters into a function that receives a single parameter.
Function currying is the process of converting a multi-parameter function into a series of single-parameter functions. The result of this transformation is that the original function can be called through a series of functions with fewer parameters.
5. Delayed call (defer)
Go functions support defer for delayed calling
defer is similar to the final clause in OOP language exception handling, and is often used to ensure the recovery and release of system resources.
defer Println("last")
Println("main body")
Println("first")
//main body
//first
//last
When using the defer function, the current actual parameter values will be passed to the formal parameters. Even if the subsequent actual parameters change, the function result will not be affected!
a := 5
defer fmt.Println(“defer 注册函数时的a值", a)
a = 10
fmt.Println(“普通函数的a值", a)
//普通函数的a值 10
//defer 注册函数时的a值 5
In addition, when using multiple defers, these defer calls are executed in first-in-last-out (FILO) order before the function returns!
name := "Naveen"
fmt.Printf("Original String: %s\n", string(name))
fmt.Printf("Reversed String: ")
for _, v := range []rune(name) {
defer fmt.Printf("%c", v)
}
//Original String: Naveen
//Reversed String: neevaN
6. Recursive functions
Formally: an executing function calls itself (direct recursion).
Recursion cannot be called without limit because the stack space is limited
There must be a statement to complete the ultimate task in the recursion
The recursive call parameters gradually approach the end condition< /span>
The purpose of recursion is to simplify the design and make the program easier to read, but it is usually less efficient
6. Structure and methods
1. Structure
1. Structure definition
Structures unify different types of data that are intrinsically related into a whole and make them related to each other.
A structure is a collection of variables, which looks like an entity from the outside.
type Employee struct{
firstName string
lastName string
age int
salary int
}
2. Labeled structure
In addition to the name and type, the fields in the structure can also have an optional tag.
The tag is a string attached to the field, used to describe the field information
The tag can also be modified according to key1:“value1” key2:“value2”
key-value pairs to provide Encoding, decoding, ORM and other conversion assistance
You can use reflection to obtain each key-value pair in the structure tag.
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string `json:"name" validate:"required"`
Age int `json:"age" validate:"min=18"`
}
func main() {
p := Person{
Name: "Alice", Age: 25}
// 获取结构体字段的标签信息
t := reflect.TypeOf(p)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Field: %s, Tag: %s\n", field.Name, field.Tag)
}
}
3. Initialization of structure variables
1. You can use field names to initialize, so there is no need to order, and unspecified fields have zero values.
emp1 := Employee{
firstName: "Sam",
age: 25,
salary: 500,
lastName: “Anderson”, //逗号不能忽略
}
2. Initialize with literals, set them all in the order in which field types are declared. If the order is incorrect or fields are missing, an error will be reported.
emp2 := Employee{
"Thomas", "Paul", 29, 800}
4. Access and modify field values
1. Use structure variable.field
emp := Employee{
"Thomas", "Paul", 29, 800}
fmt.Println(emp.age)
2. Use (*structure variable pointer).field
emp := &Employee{
"Sam", "Anderson", 55, 6000}
fmt.Println("First Name:", (*emp).firstName)
3. Use structure variable pointer.field, not supported ->
emp := &Employee{
"Sam", "Anderson", 55, 6000}
fmt.Println("First Name:", emp.firstName)
4.Anonymous fields
The field name can also be omitted in the structure field.The field name defaults to the corresponding data type name (the data type cannot be repeated)
type Person struct {
string
int
}
p := Person{
"Naveen", 50}
p.int =60
2.Method
A method is an encapsulation of the behavior of a specific type, essentially a function bound to that type.
Methods in OO languages usually have a hidden this or self pointer pointing to the object. Go exposes this hidden pointer, which is called a receiver.
func (t Type) funcName(paramList)(resultList)
func (t *Type) funcName(paramList)(resultList)
1. Method examples
type Employee struct {
name string
salary int
currency string
}
//定义方法
func (e Employee) displaySalary() {
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {
emp1 := Employee{
name: "Sam Adolf",
salary: 5000,
currency: "$",
}
emp1.displaySalary()
}
In fact, the method can be implemented using equivalent functions, as shown below:
type Employee struct {
name string
salary int
currency string
}
func displaySalary(e Employee) {
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {
emp1 := Employee{
name: "Sam Adolf",
salary: 5000,
currency: "$",
}
displaySalary(emp1)
}
Since functions can do it, why do we need methods?
In the Go language, methods and equivalent functions can accomplish similar tasks. Although they can accomplish the same task, there are some differences and applicable scenarios between methods and functions,where methods are more suitable for specific types of operations and object-oriented programming.
GO functions cannot be overloaded, so different types cannot use functions with the same name, but different types of methods can have the same name
GO does not support classes, use structures instead of classes, and structure fields are used Encapsulate object properties, methods are used to encapsulate the object's behavior
In addition, methods are not exclusive to structures, all custom types can define methods
type myInt int //自定义类型
func (a *myInt) add(b myInt) myInt {
return *a + b
}
num1 := myInt(5)
num2 := myInt(10)
sum := num1.add(num2)
7.Interface
The interface in Go language is a non-intrusive interface of the Duck model. Unlike traditional interfaces, the specific type of non-intrusive interface does not need to be explicitly declared. As long as its method set is a superset of the interface, it will be corresponding during compilation. check!
The GO interface only has method signatures, no data fields, and no function body code.
If the method set of a type is a superset of multiple interfaces, it implements multiple interfaces
1.Interface class definition
// 命令接口类型
type interfaceName interface{
//接口类型命名通常以er为后缀
methodName(paramList)(resultList)
otherInterfaceName
}
// 匿名接口类型
interface{
methodName(paramList)(resultList)
otherInterfaceName
}
And if the method set in the anonymous interface is empty, that is, interface{} is an empty interface. All types implement the empty interface and can be assigned or passed to the empty interface.
2.Interface initialization
Only declare unassigned interface variables as nil
Initialization of interface variables requires binding the interface to a specific type instance
Uninitialized interface variables cannot Only the receiver of the method can assign a value to the interface variable by calling its method
. The value of the interface variable includes the value of the underlying type and the specific type
package main
import (
"fmt"
)
// 接口定义
type Speaker interface {
Speak() string
}
// 实现接口的结构体
type Dog struct{
}
// Dog 结构体实现 Speak 方法
func (d Dog) Speak() string {
return "Woof!"
}
// 创建接口的实例
func NewSpeaker() Speaker {
return Dog{
} // 返回一个 Dog 类型,它满足了 Speaker 接口
}
func main() {
// 初始化接口并调用方法
speaker := NewSpeaker()
fmt.Println(speaker.Speak())
}
In addition, an interface can contain one or more interfaces, that is, nested interfaces
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type File interface{
ReadWrite
close() bool
}
3. Interface type assertion
Interface type assertion is used to determine whether a variable that implements an interface is of a certain type
If so, return the value of that type and true
If not, return zero value and false of this type
// interfaceName.(typeName)
var a interface{
} = 56
v, ok := a.(int)
fmt.Println(v, ok)
//56 true
var b interface{
} = true
v, ok = b.(int)
fmt.Println(v, ok)
//0 false
4. Interface type query
Interface type query uses the switch statement to determine the underlying type of the interface variable.
.(type) can only be used in switch expressions because the underlying type of a variable can only be determined by interface type assertion. Go can only determine whether the variable memory format matches a certain type and parse the value according to a certain type.
func findType(i interface{
}) {
switch i.(type) {
//.(type)只能用于switch表达式
case string:
fmt.Printf("string and value is %s\n", i.(string))
case int:
fmt.Printf("int and value is %d\n", i.(int))
default:
fmt.Printf("Unknown type\n")
}
}
findType(77)
findType(89.98)
// int and value is 77
// Unknown typ
5.Stringer interface
Stringer
The interface is an interface in the Go language. It contains only one method: String()
, is used to return the string representation of this type< /span>. This interface is usually used to customize the type of string output format.
In Go language, if a type implements the Stringer
interface, then you can use the printing method in the fmt
package (such as ) to customize the output method of this type. Println
or Sprintf
The following is the definition of the Stringer
interface:
type Stringer interface {
String() string
}
String()
The method of the in order to customize the string output of this type. methods, Stringer
interface can define their own interface returns a string. Types that implement the String()
The following is a simple example demonstrating how to use the Stringer
interface:
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
// 实现 Stringer 接口
func (p Person) String() string {
return fmt.Sprintf("%s is %d years old", p.Name, p.Age)
}
func main() {
person := Person{
"Alice", 30}
fmt.Println(person) // 输出: Alice is 30 years old
}
In this example, the Person
type implements the Stringer
interface and overrides the String()
method, so that when we When using fmt.Println
to print a variable of type Person
, the String()
method is called and the custom string format of the type is output.
Summary implements the type variable of the Stringer interface. When using the fmt.Println method to print the object, it can be output in a specified format, similar to overriding the toString method in Java. .
6.Sorter interface
The sort package of the standard library implements three methods to define sorting:
//Len() 反映元素个数的方法
//Less(i, j) 比较第 i 和 j 个元素
//Swap(i, j) 交换第 i 和 j 个元素
// 具体Sorter接口定义如下
type Sorter interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
Please implement bubble sorting based on the Sorter interface:
package main
import (
"fmt"
)
type Sorter interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
type IntArray []int
func (arr IntArray) Len() int {
return len(arr)
}
func (arr IntArray) Less(i, j int) bool {
return arr[i] < arr[j]
}
func (arr IntArray) Swap(i, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
func BubbleSort(data Sorter) {
n := data.Len()
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if data.Less(j+1, j) {
data.Swap(j, j+1)
}
}
}
}
func main() {
array := IntArray{
64, 34, 25, 12, 22, 11, 90}
fmt.Println("Unsorted array:", array)
BubbleSort(array)
fmt.Println("Sorted array:", array)
}
7. Interface characteristics
The interface feature is referred to as dynamic and static combination.
Interface static characteristics:
Supports type checking at the compilation stage: when an interface type variable is assigned, the compiler will check whether the rvalue type implements all methods in the interface method set.
Interface dynamic characteristics:
That is: using empty interface variables can use different types of variable assignments
The true type of the value stored in the interface type variable at run time. For example: the dynamic type of interface variable i in var i interface{} = 13 is int.
can be assigned to different dynamic type variables at runtime, thus supporting runtime polymorphism.
8.Reflection
The most basic information of a variable is its type and value. Reflection can check the type and value of the variable whenthe program is running
Through reflection, you can obtain the field information of the structure variable, even the tag information of the structure field.
package main
import (
"fmt"
"reflect"
)
type Person struct {
Id int //首字母大写表示公开字段
Name string
Sex string
}
func (this Person) Call() {
fmt.Println("我正在打电话")
}
func getTypeAndValue(object interface{
}) {
//动态获取对象object的类型信息
objectType := reflect.TypeOf(object)
objectValue := reflect.ValueOf(object)
fmt.Println("type =", objectType.Name())
fmt.Println("type =", objectType, "value =", objectValue)
// objectType.NumField() 获取字段的总数
for i := 0; i < objectType.NumField(); i++ {
field := objectType.Field(i)
value := objectValue.Field(i)
fmt.Printf("type %d = %v\n", i, field.Type)
fmt.Printf("name %d = %v\n", i, field.Name)
fmt.Printf("value %d = %v\n", i, value.Interface())
}
for i := 0; i < objectValue.NumMethod(); i++ {
method := objectValue.Method(i)
method.Call(nil)
}
}
func main() {
person := Person{
1, "nancy", "mail"}
getTypeAndValue(person)
}
8.Error handling
There is no exception mechanism in Go language, only error handling. Errors are handled through multiple return values of functions.
Go language errors mainly include: compile-time errors, run-time errors and logic errors
Error handling in Go language
1.Can be processed, processed by returning an error from the function
2.cannot be processed, throw an error through panic, and exit the program
1.error interface
Implement the standard mode of error handling through the error interface, and automatically call the Error() function when printing an error.
type error interface{
Error() string
}
The last return value of a function that may go wrong is an error type. Check whether the return value is nil. If so, handle the error. Otherwise, call it normally.
f, err := os.Open("/test.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(f.Name(), "opened successfully")
2.if happy path principle
The "happy path" principle is a design concept in programming. Its guiding idea is to keep the main path of the function as "happy" as possible, that is, the main work or logic of the function can be completed as quickly as possible without being disturbed by unexpected situations.
An example is to pass reasonable error checking and returns,put the error handling logic at the beginning of the function, and put the main logic and processing Place it in the main part of the function. This allows you to exit the function early and return an error, but at the same time keep the main logic inside the function body, making the main logic as happy as possible.
func PerformTask(param int) (result int, err error) {
// 错误检查放在前面
if param < 0 {
return 0, errors.New("param cannot be negative")
}
// 主逻辑放在主体内部
// 这里是函数的主要逻辑,称为快乐路径
result = param * 2
return result, nil
}
3. Custom errors
error
is a built-in interface type in the Go language. It has only one method Error()
, which is used to return a string representation of error information.
The standard libraryerrors
package provides functions for creating simple error messages.
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(6, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
result, err = divide(3, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
Additionally, you can create custom errors using the Errorf function of the fmt package!
package main
import (
"fmt"
)
func someFunction() error {
return fmt.Errorf("This is a more detailed error: %s", "specific error message")
}
func main() {
err := someFunction()
if err != nil {
fmt.Println("Error:", err)
}
}
When code duplication exists in multiple error handling locations, can use goto to centrally handle errors!
err := firstCheckError()
if err != nil {
goto onExit
}
err = secondCheckError()
if err != nil {
goto onExit
}
// 正常处理代码
onExit:
fmt.Println(err)
exitProcess()
4.panic
panic
is one of the built-in functions in the Go language that is used to cause the program to abort when an unrecoverable error occurs. panic
will stop the execution of the current function and propagate a panic signal to the caller, and then the program will be terminated.
Normally, panic
is used to handle serious errors, such as array out-of-bounds, null pointer reference, etc. When it is called, the program will stop executing the current function and start executing the defer function, and then the program will crash with the error message generated by panic
. When encountering panic
, the normal flow of the program will be broken and the current task will not continue.
During the development process, try to avoid usingpanic
. Instead, use error returns or other appropriate handling methods when it can be predicted and handled, because a>panic
is unrecoverable and can easily cause program instability.
package main
import "fmt"
func someFunc() {
// 模拟一个无法处理的错误
err := someErrorOccurred()
if err != nil {
panic("An unexpected error occurred: " + err.Error())
}
}
func main() {
fmt.Println("Starting the program.")
someFunc()
fmt.Println("End of the program.")
}
5.recover
In Go language, recover
function is used to resume the execution of the program and recover from panic state (panic). recover
will only take effect when called internally by the defer function.
Normally, recover
is used in conjunction with defer
to resume program execution if the program enters a panic state.
Here is an example of using recover
to capture and handle panic conditions:
package main
import (
"fmt"
)
func recoverDemo() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
}
}
func someFunc() {
defer recoverDemo()
// 模拟一个恐慌状态
panic("Something went wrong!")
}
func main() {
fmt.Println("Starting the program.")
someFunc()
fmt.Println("End of the program.")
}
In this example, the recoverDemo
function is executed as a defer function in someFunc
. When the someFunc
function triggers a panic state, the function in recoverDemo
captures the panic and prints out the error message. recover
Please note that recover
functions are only valid within delayed functions. Calling recover
in a non-deferred function has no effect, and the error message can only be captured when a panic condition occurs.
9. Concurrency
1. Process, thread, coroutine
A process is an independent unit for resource allocation and scheduling by the operating system when a program is running in memory
A thread is an execution entity of the process and an execution within the process. Path is the basic unit of CPU scheduling and dispatch. It is a basic unit that is smaller than a process and can run independently
Each process includes at least one thread, and the initial thread of each process is called Main thread, main thread terminates, process terminates
Coroutines are lightweight threads, and one thread can have multiple coroutines
Processes and threads are a>. Coroutines are not managed by the operating system kernel, but are completely controlled by the program, so there is no thread switching overhead. Compared with multi-threads, the greater the number, the more obvious the performance advantage of coroutines. The biggest advantage of coroutines is that they are lightweight and can easily create tens of thousands without causing system resource exhaustioncompiler level, coroutine is operating system level
2.goroutine
In the Go language, Goroutine is the basic unit of concurrent execution. They are lightweight threads in the Go runtime environment and are assigned to logical processors for execution by the Go scheduler. Goroutine operation does not depend on physical processors or operating system threads. Each logical processor (P) is responsible for running Goroutine, and multiple Ps can run on one physical processor (CPU).
3.Coroutine communication
Don't communicate via shared memory, share memory via communication
There are two common ways of communicating between coroutines:
1. Shared data: Many languages use shared memory to synchronize program data and ensure that the program is executed in a logical manner. During program execution, a process or thread may lock shared data to prevent other processes or threads from modifying it. Overall programming complexity is high
2. Message mechanism: Each concurrent unit is an independent individual, and the data of multiple concurrent units is not shared. Data is synchronized through message communication.
4.channel channel
Channel is a special type, and only one goroutine can access the channel to send and obtain data at the same time.
Channel writing and reading use the <- operator
Writing: Channel<-Variable
Reading: Variable<- channel
5. Buffer channel
Channels include unbuffered channels and buffered channels
Unbuffered channel make(chan datatype)
Buffered channel make(chan datatype, capacity)
The unbuffered channel can only store one message. The buffered channel can store n messages according to the capacity parameter of the make function and read them out according to FIFO.
func receiver(c chan string) {
for msg := range c {
fmt.Println(msg)
}
}
func main() {
messages := make(chan string, 2)
messages <- "hello"
messages <- "world"
go receiver(messages)
time.Sleep(time.Second * 1)
}//hello world
Additionally, you can use built-in functions to return buffer channel status
len() gets the current buffer number of the channel
cap() gets the channel buffer capacity
ch := make(chan string, 3)
ch <- "naveen"
ch <- "paul"
fmt.Println("capacity is", cap(ch))
fmt.Println("length is", len(ch))
fmt.Println("read value", <-ch)
fmt.Println("new length is", len(ch))
//capacity is 3
//length is 2
//read value naveen
//new length is 1
Unbuffered channel, writes wait for reads, reads wait for writes, and are blocked until both parties are ready
There is a buffered channel, writes will wait when the channel is full, and reads will wait when the channel is empty.
6. Close the channel
To close the channel, use the built-in function close(), which actually closes writing, that is, the sender tells the receiver that no more data will be sent to the channel.
The receiver can obtain the parameters of whether the channel is closed while the channel is receiving data.
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for {
v, ok := <-ch
if ok == false {
break
}
fmt.Println("Received ", v, ok)
}
}
In addition,for range can automatically determine whether the channel is closed. The specific code is as follows:
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for v := range ch {
fmt.Println("Received ",v)
}
}
7.WaitGroup
sync.WaitGroup
Method used to wait for a set of Go coroutines to complete before executing the main program. It provides a simple mechanism so that the main program knows when all other coroutines have completed execution.
When using sync.WaitGroup
, there are three main functions:
Add(int)
: Increase the number of coroutines to wait for.Done()
: Marks the completed coroutine.Wait()
: Wait for all coroutines to complete.
Usually, the Add
function is used to count the number of coroutines to wait, and then use Done
in the function of the coroutine to mark that it has been executed, and finally Use Wait
to block the main program until all coroutines are executed.
The following is an example demonstrating the use of sync.WaitGroup
:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 标志协程完成
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second) // 模拟工作
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1) // 增加等待的协程数量
go worker(i, &wg)
}
wg.Wait() // 等待协程执行完成
fmt.Println("All workers have finished")
}
In this example, we start five coroutines, each goroutine simulates some work (simulated by time.Sleep
). Add
is used to increase the number of coroutines to wait, Done
marks that the coroutine has been executed, and Wait
blocks the main program until All coroutines have been executed.
8. Guess the number example
Here is an example of using coroutines. The specific example is as follows: Interested friends can try it
9. Timer
Communication between coroutines requires setting up auxiliary mechanisms such as timeout
One-time timer: The timer only counts once and stops when it is finished.
package main
import (
"fmt"
"time"
)
func main() {
timer1 := time.NewTimer(2 * time.Second)
<-timer1.C // 阻塞等待定时器信号
fmt.Println("Timer 1 expired")
timer2 := time.NewTimer(1 * time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop() // 停止定时器2
if stop2 {
fmt.Println("Timer 2 stopped")
}
}
10.Timer Ticker
Periodic timer: The timer counts periodically and will run permanently unless it is actively stopped.
In Go language, time.Ticker
is a tool for repeating interval trigger operations. Unlike time.Timer
, time.Ticker
repeatedly sends time events to the channel at certain intervals.
The main methods are as follows:
func NewTicker(d Duration) *Ticker 指定一个时间创建一个Ticker , Ticker一经创建便开始计时,不需要额外的启动命令
func (t *Ticker) Stop() 停止计时,但管道不会被关闭
Sample code looks like this:
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
fmt.Println("Ticker ticked")
}
}
}
11.select
Multiplexing is the transmission of multiple signals or data streams on one channel, such as network cables
select borrows the concept of network multiplexing, is used to monitor multiple channels and respond to multiple channels at the same time
If multiple channels are not writable or readable, select will block
If one channel is writable or readable, select will execute the channel statement
There are multiple channels that are writable or readable, select will randomly select one of them for execution
select
The statement is a key tool in the Go language for handling channel operations. It can monitor multiple channel operations at the same time. Once a channel is operable (a message can be received or sent), the corresponding case statement will be executed. The select
statement is somewhat similar to the switch
statement, but is specifically used for channel operations.
Here is an example that demonstrates the use of the select
statement:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Received", msg1)
case msg2 := <-ch2:
fmt.Println("Received", msg2)
}
}
}
In this example, messages are sent through two coroutines to two different channels ch1
and ch2
. The select
statement will monitor the status of these two channels. Once there is data to receive, the corresponding case statement will be executed and the received message will eventually be output.
12.Mutex
Multiple threads competing to use a variable at the same time may lead to out-of-control results
mutex, a mutex lock, is used to ensure that a certain variable can only be accessed by one thread at any time; mutex uses Lock() and Unlock() to create a critical section of resources. The code in this section is thread-safe. At any point in time, there can only be one goroutine executing the code in this range.
Mutex can also be replaced by a channel. The bottom layer of the channel is based on mutex, that is, mutex has higher performance. Generally, mutex is used if thread interaction data is not involved. Channels are used if other performance requirements are not sensitive.
package main
import (
"fmt"
"sync"
)
var count = 0
var mutex sync.Mutex
func increment() {
mutex.Lock() // 通过 Lock() 方法锁住共享资源
count++
mutex.Unlock() // 通过 Unlock() 方法解锁共享资源
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Count:", count)
}
13.RWMutex
Mutex When there is a lot of concurrency, only one coroutine holds the lock at the same time, and the others are blocked and waiting, and the performance decreases
RWMutex adds read and write signals based on Mutex The amount, and the number of read locks similar to reference counting is used, so that multiple coroutines can hold read locks, which is suitable for applications with a certain amount of concurrency and more reads and less writes.
Notice:
You can apply for multiple read locks in RWMutex. If there is a read lock, applying for a write lock will be blocked.
As long as there is a write lock, subsequent applications for read locks and write locks will be blocked.
The main methods are as follows:
func (rw *RWMutex) Lock() //申请写锁
func (rw *RWMutex) Unlock() //释放写锁
func (rw *RWMutex) RLock() //申请读锁
func (rw *RWMutex) RUnlock()//释放读锁
package main
import (
"fmt"
"sync"
)
var sharedData int
var rwMutex sync.RWMutex
func readData() {
rwMutex.RLock() // 读取共享资源时使用 RLock() 方法
defer rwMutex.RUnlock()
fmt.Println("Read Data:", sharedData)
}
func writeData(value int) {
rwMutex.Lock() // 写入共享资源时使用 Lock() 方法
defer rwMutex.Unlock()
sharedData = value
fmt.Println("Write Data:", value)
}
func main() {
// 读取数据
for i := 0; i < 5; i++ {
go readData()
}
// 写入数据
for i := 0; i < 5; i++ {
go writeData(i)
}
// 等待所有协程执行完毕
fmt.Scanln()
}