go语法笔记
hello world
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { var firstname string fmt.Scanln(&firstName) fmt.Println(firstName) }
println
is an built-in function (into the runtime) which may eventually be removed, while the fmt
package is in the standard library, which will persist. See the spec on that topic.
For language developers it is handy to have a println
without dependencies, but the way to go is to use the fmt
package or something similar (log
for example).
变量
定义变量
1 2 3 4 5 6 7 8 9 10 11 12 var firstName string var (firstName,lastName = "harry" ,"smith" ) func main () { firstName, lastName := "John" , "Doe" fmt.Println(firstName, lastName) }
定义常量
1 2 3 4 5 6 7 const httpstatus = 200 const ( StatusOK = 0 StatusConnectionReset = 1 StatusOtherError = 2 )
如果变量定义了未使用go会抛出错误
数据类型 Go 有四类数据类型:
基本类型:数字、字符串和布尔值
聚合类型:数组和结构
引用类型:指针、切片、映射、函数和通道
接口类型:接口
数字 一般使用 int (当计算机是32位时表示32位的int , 64位时位64位int)
Go 还提供了 int8
、int16
、int32
和 int64
类型,其大小分别为 8、16、32 或 64 位的整数
只有float32 float64 两种
布耳
字符串 string
1 2 fullName := "John Doe \t(alias \"Foo\")\n" fmt.Println(fullName)
转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var integer16 int16 = 127 var integer32 int32 = 32767 fmt.Println(int32 (integer16) + integer32) import "strconv" func main () { i, _ := strconv.Atoi("-42" ) s := strconv.Itoa(-42 ) fmt.println (i, s) }
变量默认值
int
类型的 0
(及其所有子类型,如 int64
)
float32
和 float64
类型的 +0.000000e+000
bool
类型的 false
string
类型的空值
数组 要在 Go 中声明数组,必须定义其元素的数据类型以及该数组可容纳的元素数目。 然后,可采用下标表示法访问数组中的每个元素,其中第一个元素是 0,最后一个元素是数组长度减去 1(长度 - 1)。
1 2 3 4 5 6 7 8 var a [3 ]int a[1 ] = 10 cities := [5 ]string {"New York" , "Paris" , "Berlin" , "Madrid" } fmt.Println("Cities:" , cities) var twoD [3 ][5 ]int
slice 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 months := []string {"January" , "February" , "March" , "April" , "May" , "June" , "July" , "August" , "September" , "October" , "November" , "December" } quarter1 := months[0 :3 ] quarter2 := months[3 :6 ] quarter3 := months[6 :9 ] quarter4 := months[9 :12 ] fmt.Println(quarter1, len (quarter1), cap (quarter1)) fmt.Println(quarter2, len (quarter2), cap (quarter2)) fmt.Println(quarter3, len (quarter3), cap (quarter3)) fmt.Println(quarter4, len (quarter4), cap (quarter4)) [January February March] 3 12 [April May June] 3 9 [July August September] 3 6 [October November December] 3 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var numbers []int for i := 0 ; i < 10 ; i++ { numbers = append (numbers, i) fmt.Printf("%d\tcap=%d\t%v\n" , i, cap (numbers), numbers) } 0 cap =1 [0 ]1 cap =2 [0 1 ]2 cap =4 [0 1 2 ]3 cap =4 [0 1 2 3 ]4 cap =8 [0 1 2 3 4 ]5 cap =8 [0 1 2 3 4 5 ]6 cap =8 [0 1 2 3 4 5 6 ]7 cap =8 [0 1 2 3 4 5 6 7 ]8 cap =16 [0 1 2 3 4 5 6 7 8 ]9 cap =16 [0 1 2 3 4 5 6 7 8 9 ]
1 2 3 4 5 letters := []string {"A" , "B" , "C" , "D" , "E" } slice2 := make ([]string , 3 ) copy (slice2, letters[1 :4 ])fmt.Println(slice2)
map(key-value) 1 2 3 4 5 6 studentsAge := map [string ]int { "john" : 32 , "bob" : 31 , } fmt.Println(studentsAge)
map操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 studentsAge := make (map [string ]int ) studentsAge["john" ] = 32 studentsAge := make (map [string ]int ) studentsAge["john" ] = 32 studentsAge["bob" ] = 31 age, exist := studentsAge["christy" ] if exist { fmt.Println("Christy's age is" , age) } else { fmt.Println("Christy's age couldn't be found" ) } delete (studentAge,"john" )delete (studentsAge, "christy" )for name, age := range studentsAge { fmt.Printf("%s\t%d\n" , name, age) }
struct 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport "fmt" type Person struct { ID int FirstName string LastName string Address string } type Employee struct { Person ManagerID int } type Contractor struct { Person CompanyID int } func main () { employee := Employee{} employee.LastName = "Doe" fmt.Println(employee.LastName) }
json序列化及其反序列化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package mainimport ( "encoding/json" "fmt" ) type Person struct { ID int FirstName string `json:"name"` LastName string Address string `json:"address,omitempty"` } type Employee struct { Person ManagerID int } type Contractor struct { Person CompanyID int } func main () { employees := []Employee{ { Person: Person{ LastName: "Doe" , FirstName: "John" , Address: "balabala" , }, }, { Person: Person{ LastName: "Campbell" , FirstName: "David" , }, }, } data, _ := json.Marshal(employees) fmt.Printf("%s\n" , data) var decoded []Employee json.Unmarshal(data, &decoded) fmt.Printf("%v\n" , decoded) } [{"ID" :0 ,"name" :"John" ,"LastName" :"Doe" ,"address" :"balabala" ,"ManagerID" :0 },{"ID" :0 ,"name" :"David" ,"LastName" :"Campbell" ,"ManagerID" :0 }] [{{0 John Doe balabala} 0 } {{0 David Campbell } 0 }]
序列化详细知识:https://sanyuesha.com/2018/05/07/go-json/
函数 1 2 3 func name (parameters) (results) { body-content }
函数类似js的语法
可以带多个返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "fmt" "os" "strconv" ) func main () { add_sum, mul_sum := calc(os.Args[1 ], os.Args[2 ]) fmt.Println(add_sum, mul_sum) } func calc (number1 string , number2 string ) (int , int ) { int1, _ := strconv.Atoi(number1) int2, _ := strconv.Atoi(number2) sum := int1 + int2 mul := int1 * int2 return sum, mul }
指针
&
运算符使用其后对象的地址。
*
运算符取消引用指针。 也就是说,你可以前往指针中包含的地址访问其中的对象。
1 2 3 4 5 6 7 8 9 10 11 12 package mainfunc main () { firstName := "John" updateName(&firstName) fmt.println (firstName) } func updateName (name *string ) { *name = "David" }
#### 匿名函数 1、不带参数
1 2 3 4 5 6 7 func main () { f:=func () { fmt.Println("hello world" ) } f() fmt.Printf("%T\n" , f) }
2、带参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { f:=func (args string ) { fmt.Println(args) } f("hello world" ) (func (args string ) { fmt.Println(args) })("hello world" ) func (args string ) { fmt.Println(args) }("hello world" ) }
3、带返回值
1 2 3 4 5 6 7 func main () { f:=func () string { return "hello world" } a:=f() fmt.Println(a) }
4、多个匿名函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { f1,f2:=F(1 ,2 ) fmt.Println(f1(4 )) fmt.Println(f2()) } func F (x, y int ) (func (int ) int ,func () int ) { f1 := func (z int ) int { return (x + y) * z / 2 } f2 := func () int { return 2 * (x + y) } return f1,f2 }
闭包(closure) 闭包:说白了就是函数的嵌套,内层的函数可以使用外层函数的所有变量,即使外层函数已经执行完毕 。
示例:
1、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { a := Fun() b:=a("hello " ) c:=a("hello " ) fmt.Println(b) fmt.Println(c) } func Fun () func (string ) string { a := "world" return func (args string ) string { a += args return a } }
2、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func main () { a := Fun() d := Fun() b:=a("hello " ) c:=a("hello " ) e:=d("hello " ) f:=d("hello " ) fmt.Println(b) fmt.Println(c) fmt.Println(e) fmt.Println(f) } func Fun () func (string ) string { a := "world" return func (args string ) string { a += args return a } }
注意两次调用F(),维护的不是同一个a变量。
3、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func main () { a := F() a[0 ]() a[1 ]() a[2 ]() } func F () []func () { b := make ([]func () , 3, 3) for i := 0 ; i < 3 ; i++ { b[i] = func () { fmt.Println(&i,i) } } return b }
闭包通过引用的方式使用外部函数的变量。例中只调用了一次函数F,构成一个闭包,i 在外部函数B中定义,所以闭包维护该变量 i ,a[0]、a[1]、a[2]中的 i 都是闭包中 i 的引用。因此执行,i 的值已经变为3,故再调用a0 时的输出是3而不是0。
4、如何避免上面的BUG ,用下面的方法,注意和上面示例对比。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport "fmt" func main () { a := F() a[0 ]() a[1 ]() a[2 ]() } func F () []func () { b := make ([]func () , 3, 3) for i := 0 ; i < 3 ; i++ { b[i] = (func (j int ) func () { return func () { fmt.Println(&j, j) } })(i) } return b }
或者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" func main () { a := F() a[0 ]() a[1 ]() a[2 ]() } func F () []func () { b := make ([]func () , 3, 3) for i := 0 ; i < 3 ; i++ { j := i b[i] = func () { fmt.Println(&j, j) } } return b }
每次 操作仅将匿名函数放入到数组中,但并未执行,并且引用的变量都是 i,随着 i 的改变匿名函数中的 i 也在改变,所以当执行这些函数时,他们读取的都是环境变量 i 最后一次的值。解决的方法就是每次复制变量 i 然后传到匿名函数中,让闭包的环境变量不相同。
5、
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func main () { fmt.Println(F()) } func F () (r int ) { defer func () { r++ }() return 1 }
输出结果为2,即先执行r=1 ,再执行r++。
6、递归函数
还有一种情况就是必须用都闭包,就是递归函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func F (i int ) int { if i <= 1 { return 1 } return i * F(i-1 ) } func main () { var i int = 3 fmt.Println(i, F(i)) }
7、斐波那契数列(Fibonacci)
这个数列从第3项开始,每一项都等于前两项之和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" func fibonaci (i int ) int { if i == 0 { return 0 } if i == 1 { return 1 } return fibonaci(i-1 ) + fibonaci(i-2 ) } func main () { var i int for i = 0 ; i < 10 ; i++ { fmt.Printf("%d\n" , fibonaci(i)) } }
小结:
匿名函数和闭包其实是一回事儿,匿名函数就是闭包。匿名函数给编程带来灵活性的同时也容易产生bug,在使用过程当中要多注意函数的参数,及可接受的参数的问题。
包 本地包 go中模块可以用来导入到其他的包中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package calculatorvar logMessage = "[LOG]" var Version = "1.1" func internalSum (number int ) int { return number - 1 } func Sum (number1, number2 int ) int { return number1 + number2 }
编写完之后使用 go mod init 给模块取个名字
,比如go mod init github.com/myuser/calculator
当前目录下会生成一个go.mod这里是相当于这个”计算器”包的依赖和环境类似py的requirements.txt
依赖然后就可以在main包中调用这个包,使用import
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" "github.com/myuser/calculator" ) func main () { total := calculator.Sum(3 , 5 ) fmt.Println(total) fmt.Println("Version: " , calculator.Version) }
然后go mod init helloworld
,然后生成了这个包的依赖
给go.mod增加几行
1 2 3 4 5 require github.com/myuser/calculator v0.0.0 replace github.com/myuser/calculator => ../calculator
调用本地包
调用第三方包 使用go get xxx
来进行第三方包的安装,go get的就是git clone和go install的两个命令的集合
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "rsc.io/quote" ) func main () { fmt.Println(quote.Hello()) }
然后go run main.go
即可
有时可能需要重建依赖go mod tidy
vscode中如果导入了第三包出现飘红情况,是打开的文件路径不对,找到和当前项目go.mod所在的目录打开文件夹
流程控制语句 if if条件表达式可以不带小括号
但是大括号不能省略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func somenumber () int { return -7 } func main () { if num := somenumber(); num < 0 { fmt.Println(num, "is negative" ) } else if num < 10 { fmt.Println(num, "has 1 digit" ) } else { fmt.Println(num, "has multiple digits" ) } fmt.Println(num) }
switch 在 Go 中,当逻辑进入某个 case 时,它会退出 switch
块(满足条件后退出switch块),除非你显式停止它。
若要使逻辑进入到下一个紧邻的 case,请使用 fallthrough
关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "fmt" ) func main () { switch num := 15 ; { case num < 50 : fmt.Printf("%d is less than 50\n" , num) fallthrough case num > 100 : fmt.Printf("%d is greater than 100\n" , num) fallthrough case num < 200 : fmt.Printf("%d is less than 200" , num) } }
运行代码并分析输出:
输出
1 2 3 15 is less than 50 15 is greater than 100 15 is less than 200
你是否看到错误?
请注意,由于 num
为 15(小于 50),因此它与第一个 case 匹配。 但是,num
不大于 100。 由于第一个 case
语句包含 fallthrough
关键字,因此逻辑会立即转到下一个 case
语句,而不会对该 case 进行验证。 因此,在使用 fallthrough
关键字时必须谨慎。 该代码产生的行为可能不是你想要的。
for 最简例子
1 2 3 4 5 6 7 8 func main () { sum := 0 for i := 1 ; i <= 100 ; i++ { sum += i } fmt.Println("sum of 1..100 is" , sum) }
随机数产生
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "math/rand" "time" ) func main () { var num int32 sec := time.Now().Unix() rand.Seed(sec) for { fmt.Print("Writting inside the loop..." ) if num = rand.Int31n(10 ); num == 5 { fmt.Println("finish!" ) break } fmt.Println(num) } }
continue中规中矩没啥区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { sum := 0 for num := 1 ; num <= 100 ; num++ { if num%5 == 0 { continue } sum += num } fmt.Println("The sum of 1 to 100, but excluding numbers divisible by 5, is" , sum) }
defer panic recover 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport "fmt" func highlow (high int , low int ) { if high < low { fmt.Println("Panic!" ) panic ("highlow() low greater than high" ) } defer fmt.Println("Deferred: highlow(" , high, "," , low, ")" ) fmt.Println("Call: highlow(" , high, "," , low, ")" ) highlow(high, low+1 ) } func main () { defer func () { handler := recover () if handler != nil { fmt.Println("main(): recover" , handler) } }() highlow(2 , 0 ) fmt.Println("Program finished successfully!" ) }
1 2 3 4 5 6 7 8 Call: highlow( 2 , 0 ) Call: highlow( 2 , 1 ) Call: highlow( 2 , 2 ) Panic! Deferred: highlow( 2 , 2 ) Deferred: highlow( 2 , 1 ) Deferred: highlow( 2 , 0 ) main(): recover highlow() low greater than high
错误处理 错误处理策略 当函数返回错误时,该错误通常是最后一个返回值。 正如上一部分所介绍的那样,调用方负责检查是否存在错误并处理错误。 因此,一个常见策略是继续使用该模式在子例程中传播错误。 例如,子例程(如上一示例中的 getInformation
)可能会将错误返回给调用方,而不执行其他任何操作,如下所示:
1 2 3 4 5 6 7 func getInformation (id int ) (*Employee, error) { employee, err := apiCallEmployee(1000 ) if err != nil { return nil , err } return employee, nil }
你可能还需要在传播错误之前添加更多信息。 为此,可以使用 fmt.Errorf()
函数,该函数与我们之前看到的函数类似,但它返回一个错误。 例如,你可以向错误添加更多上下文,但仍返回原始错误,如下所示:
1 2 3 4 5 6 7 func getInformation (id int ) (*Employee, error) { employee, err := apiCallEmployee(1000 ) if err != nil { return nil , fmt.Errorf("Got an error when getting the employee information: %v" , err) } return employee, nil }
另一种策略是在错误为暂时性错误时运行重试逻辑。 例如,可以使用重试策略调用函数三次并等待两秒钟,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 func getInformation (id int ) (*Employee, error) { for tries := 0 ; tries < 3 ; tries++ { employee, err := apiCallEmployee(1000 ) if err == nil { return employee, nil } fmt.Println("Server is not responding, retrying ..." ) time.Sleep(time.Second * 2 ) } return nil , fmt.Errorf("server has failed to respond to get the employee information" ) }
最后,可以记录错误并对最终用户隐藏任何实现详细信息,而不是将错误打印到控制台。 我们将在下一模块介绍日志记录。 现在,让我们看看如何创建和使用自定义错误。
创建可重用的错误 有时错误消息数会增加,你需要维持秩序。 或者,你可能需要为要重用的常见错误消息创建一个库。 在 Go 中,你可以使用 errors.New()
函数创建错误并在若干部分中重复使用这些错误,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 var ErrNotFound = errors.New("Employee not found!" )func getInformation (id int ) (*Employee, error) { if id != 1001 { return nil , ErrNotFound } employee := Employee{LastName: "Doe" , FirstName: "John" } return &employee, nil }
getInformation
函数的代码外观更优美,而且如果需要更改错误消息,只需在一个位置更改即可。 另请注意,惯例是为错误变量添加 Err
前缀。
最后,如果你具有错误变量,则在处理调用方函数中的错误时可以更具体。 errors.Is()
函数允许你比较获得的错误的类型,如下所示:
1 2 3 4 5 6 7 employee, err := getInformation(1000 ) if errors.Is(err, ErrNotFound) { fmt.Printf("NOT FOUND: %v\n" , err) } else { fmt.Print(employee) }
你还需要了解何时使用 panic。 我们已介绍 panic
和 recover
的工作原理。 仅当明确需要停止程序时,才应使用这些函数。 有时,即使你正确处理了错误,程序也可能会停止响应。 但这应该是异常,而不是规则。
log log包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import ( "log" ) func main () { log.Print("Hey, I'm a log!" ) log.SetPrefix("main(): " ) log.Fatal("Hey, I'm an error log!" ) log.Panic("Hey, I'm an error log!" ) } 2021 /10 /18 09 :59 :58 Hey, I'm a log! main(): 2021/10/18 09:59:58 Hey, I' m an error log!exit status 1
记录到文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "log" "os" ) func main () { file, err := os.OpenFile("info.log" , os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644 ) if err != nil { log.Fatal(err) } defer file.Close() log.SetOutput(file) log.Print("Hey, I'm a log!" ) }
zerolog 重构依赖go mod tidy
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main () { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Print("Hey! I'm a log message!" ) }
另一有用功能是你可以快速添加上下文数据,如下所示:
Go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main () { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Debug(). Int("EmployeeID" , 1001 ). Msg("Getting employee information" ) log.Debug(). Str("Name" , "John" ). Send() }
运行前面的代码时,将看到以下输出:
输出
1 2 {"level":"debug","EmployeeID":1001,"time":1609855731,"message":"Getting employee information"} {"level":"debug","Name":"John","time":1609855731}
method(oop) usage:
1 2 3 func (variable type ) MethodName (parameters ...) { }
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package mainimport "fmt" type triangle struct { size int } type coloredTriangle struct { triangle color string } type square struct { size int } func (t triangle) perimeter () int { return t.size * 3 } func (t coloredTriangle) perimeter () int { return t.size * 3 * 2 } func (s square) perimeter () int { return s.size * 4 } func main () { t := triangle{3 } t1 := coloredTriangle{triangle{3 }, "blue" } s := square{4 } fmt.Println("Perimeter (triangle):" , t.perimeter()) fmt.Println("Perimeter (triangle colored):" , t1.size, t1.perimeter()) fmt.Println("Perimeter (normal)" , t1.triangle.perimeter()) fmt.Println("Perimeter (square):" , s.perimeter()) } Perimeter (triangle): 9 Perimeter (triangle colored): 3 18 Perimeter (normal) 9 Perimeter (square): 16
interface(oop) Go 中的接口是一种用于表示其他类型的行为的数据类型。 接口类似于对象应满足的蓝图或协定。 在你使用接口时,你的基本代码将变得更加灵活、适应性更强,因为你编写的代码未绑定到特定的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package mainimport ( "fmt" "math" ) type Shape interface { Perimeter() float64 Area() float64 } type Square struct { size float64 } type Circle struct { radius float64 } func (s Square) Area () float64 { return s.size * s.size } func (s Square) Perimeter () float64 { return s.size * 4 } func (c Circle) Area () float64 { return math.Pi * c.radius * c.radius } func (c Circle) Perimeter () float64 { return 2 * math.Pi * c.radius } func printInformation (s Shape) { fmt.Printf("%T\n" , s) fmt.Println("Area: " , s.Area()) fmt.Println("Perimeter:" , s.Perimeter()) fmt.Println() } func main () { var s Shape = Square{3 } printInformation(s) var c Shape = Circle{6 } printInformation(c) }
扩展现有实现 假设你具有以下代码,并且希望通过编写负责处理某些数据的 Writer
方法的自定义实现来扩展其功能。
通过使用以下代码,你可以创建一个程序,此程序使用 GitHub API 从 Microsoft 获取三个存储库:
Go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" "io" "net/http" "os" ) func main () { resp, err := http.Get("https://api.github.com/users/microsoft/repos?page=15&per_page=5" ) if err != nil { fmt.Println("Error:" , err) os.Exit(1 ) } io.Copy(os.Stdout, resp.Body) }
运行前面的代码时,你会收到类似于以下输出的内容(已缩短以便改善可读性):
输出
1 2 [{"id":276496384,"node_id":"MDEwOlJlcG9zaXRvcnkyNzY0OTYzODQ=","name":"-Users-deepakdahiya-Desktop-juhibubash-test21zzzzzzzzzzz","full_name":"microsoft/-Users-deepakdahiya-Desktop-juhibubash-test21zzzzzzzzzzz","private":false,"owner":{"login":"microsoft","id":6154722,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTQ3MjI=","avatar_url":"https://avatars2.githubusercontent.com/u/6154722?v=4","gravatar_id":"","url":"https://api.github.com/users/microsoft","html_url":"https://github.com/micro ....
请注意,io.Copy(os.Stdout, resp.Body)
调用是指将通过对 GitHub API 的调用获取的内容打印到终端。 假设你想要写入自己的实现以缩短你在终端中看到的内容。 在查看 io.Copy
函数的源 时,你将看到以下内容:
Go
1 func Copy (dst Writer, src Reader) (written int64 , err error)
如果你深入查看第一个参数 dst Writer
的详细信息,你会注意到 Writer
是 接口 :
Go
1 2 3 type Writer interface { Write(p []byte ) (n int , err error) }
你可以继续浏览 io
程序包的源代码,直到找到 Copy
调用 Write
方法 的位置。 我们暂时不做任何处理。
由于 Writer
是接口,并且是 Copy
函数需要的对象,你可以编写 Write
方法的自定义实现。 因此,你可以自定义打印到终端的内容。
实现接口所需的第一项操作是创建自定义类型。 在这种情况下,你可以创建一个空结构,因为你只需按如下所示编写自定义 Write
方法即可:
Go
1 type customWriter struct {}
现在,你已准备就绪,可开始编写自定义 Write
函数。 此时,你还需要编写一个结构,以便将 JSON 格式的 API 响应解析为 Golang 对象。 你可以使用“JSON 转 Go”站点从 JSON 有效负载创建结构。 因此,Write
方法可能如下所示:
Go
1 2 3 4 5 6 7 8 9 10 11 12 type GitHubResponse []struct { FullName string `json:"full_name"` } func (w customWriter) Write (p []byte ) (n int , err error) { var resp GitHubResponse json.Unmarshal(p, &resp) for _, r := range resp { fmt.Println(r.FullName) } return len (p), nil }
最后,你必须修改 main()
函数以使用你的自定义对象,具体如下所示:
Go
1 2 3 4 5 6 7 8 9 10 func main () { resp, err := http.Get("https://api.github.com/users/microsoft/repos?page=15&per_page=5" ) if err != nil { fmt.Println("Error:" , err) os.Exit(1 ) } writer := customWriter{} io.Copy(writer, resp.Body) }
在运行程序时,你将会看到以下输出:
输出
1 2 3 4 5 microsoft/aed-blockchain-learn-content microsoft/aed-content-nasa-su20 microsoft/aed-external-learn-template microsoft/aed-go-learn-content microsoft/aed-learn-template
由于你写入的自定义 Write
方法,输出效果现在更好。 以下是程序的最终版本:
Go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package mainimport ( "encoding/json" "fmt" "io" "net/http" "os" ) type GitHubResponse []struct { FullName string `json:"full_name"` } type customWriter struct {}func (w customWriter) Write (p []byte ) (n int , err error) { var resp GitHubResponse json.Unmarshal(p, &resp) for _, r := range resp { fmt.Println(r.FullName) } return len (p), nil } func main () { resp, err := http.Get("https://api.github.com/users/microsoft/repos?page=15&per_page=5" ) if err != nil { fmt.Println("Error:" , err) os.Exit(1 ) } writer := customWriter{} io.Copy(writer, resp.Body) }
web api 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport ( "fmt" "log" "net/http" ) type dollars float32 func (d dollars) String () string { return fmt.Sprintf("$%.2f" , d) } type database map [string ]dollarsfunc (db database) ServeHTTP (w http.ResponseWriter, req *http.Request) { for item, price := range db { fmt.Fprintf(w, "%s: %s\n" , item, price) } } func main () { db := database{"Go T-Shirt" : 25 , "Go Jacket" : 55 } log.Fatal(http.ListenAndServe("localhost:8000" , db)) } Go Jacket: $55.00 Go T-Shirt: $25.00
并发 无缓冲channel 无缓冲 channel 同步通信。 它们保证每次发送数据时,程序都会被阻止,直到有人从 channel 中读取数据。
无缓冲匿名函数实现并发打印1-10
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { ch := make (chan int ) for i := 0 ; i < 10 ; i++ { go func (n int ) { ch <- n return }(i) } for j := 0 ; j < 10 ; j++ { fmt.Print(<-ch) } }
无缓冲非匿名函数实现并发打印1-10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { ch := make (chan int ) for i := 0 ; i < 10 ; i++ { go run_10(i, ch) } for j := 0 ; j < 10 ; j++ { fmt.Print(<-ch) } } func run_10 (n int , ch chan int ) { ch <- n return }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package mainimport ( "fmt" "net/http" "time" ) func main () { start := time.Now() apis := []string { "https://management.azure.com" , "https://dev.azure.com" , "https://api.github.com" , "https://outlook.office.com/" , "https://api.somewhereintheinternet.com/" , "https://graph.microsoft.com" , } ch := make (chan string ) for _, api := range apis { go checkAPI(api, ch) } for i := 0 ; i < len (apis); i++ { fmt.Print(<-ch) } elapsed := time.Since(start) fmt.Printf("Done! It took %v seconds!\n" , elapsed.Seconds()) } func checkAPI (api string , ch chan string ) { _, err := http.Get(api) if err != nil { ch <- fmt.Sprintf("ERROR: %s is down!\n" , api) return } ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n" , api) }
有缓冲 channel 将发送和接收操作解耦。 它们不会阻止程序,但你必须小心使用,因为可能最终会导致死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { size := 2 ch := make (chan string , size) send(ch, "one" ) send(ch, "two" ) go send(ch, "three" ) go send(ch, "four" ) fmt.Println("All data sent to the channel ..." ) for i := 0 ; i < 4 ; i++ { fmt.Println(<-ch) } fmt.Println("Done!" ) }
channel方向
example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" func send (ch chan <- string , message string ) { fmt.Printf("Sending: %#v\n" , message) ch <- message } func read (ch <-chan string ) { fmt.Printf("Receiving: %#v\n" , <-ch) } func main () { ch := make (chan string , 1 ) send(ch, "Hello World!" ) read(ch) }
多路复用 最后,让我们讨论一个关于如何在使用 select
关键字的同时与多个 channel 交互的简短主题。 有时,在使用多个 channel 时,需要等待事件发生。 例如,当程序正在处理的数据中出现异常时,可以包含一些逻辑来取消操作。
select
语句的工作方式类似于 switch
语句,但它适用于 channel。 它会阻止程序的执行,直到它收到要处理的事件。 如果它收到多个事件,则会随机选择一个。
select
语句的一个重要方面是,它在处理事件后完成执行。 如果要等待更多事件发生,则可能需要使用循环。
让我们使用以下程序来看看 select
的运行情况:
Go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "fmt" "time" ) func process (ch chan string ) { time.Sleep(3 * time.Second) ch <- "Done processing!" } func replicate (ch chan string ) { time.Sleep(1 * time.Second) ch <- "Done replicating!" } func main () { ch1 := make (chan string ) ch2 := make (chan string ) go process(ch1) go replicate(ch2) for i := 0 ; i < 2 ; i++ { select { case process := <-ch1: fmt.Println(process) case replicate := <-ch2: fmt.Println(replicate) } } }
运行程序时,将看到以下输出:
输出
1 2 Done replicating! Done processing!
请注意,replicate
函数先完成。 这就是你在终端中先看到其输出的原因。 main 函数存在一个循环,因为 select
语句在收到事件后立即结束,但我们仍在等待 process
函数完成。
通过并发实现fib数列(一个有缓存channel)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport ( "fmt" "math/rand" "time" ) func fib (number float64 , ch chan string ) { x, y := 1.0 , 1.0 for i := 0 ; i < int (number); i++ { x, y = y, x+y } r := rand.Intn(3 ) time.Sleep(time.Duration(r) * time.Second) ch <- fmt.Sprintf("Fib(%v): %v\n" , number, x) } func main () { start := time.Now() size := 15 ch := make (chan string , size) for i := 0 ; i < size; i++ { go fib(float64 (i), ch) } for i := 0 ; i < size; i++ { fmt.Printf(<-ch) } elapsed := time.Since(start) fmt.Printf("Done! It took %v seconds!\n" , elapsed.Seconds()) }
输入quit退出运算,不然进行fib的计算(两个无缓存channel)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ( "fmt" "time" ) var quit = make (chan bool )func fib (c chan int ) { x, y := 1 , 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("Done calculating Fibonacci!" ) return } } } func main () { start := time.Now() command := "" data := make (chan int ) go fib(data) for { num := <-data fmt.Println(num) fmt.Scanf("%s" , &command) if command == "quit" { quit <- true break } } time.Sleep(1 * time.Second) elapsed := time.Since(start) fmt.Printf("Done! It took %v seconds!\n" , elapsed.Seconds()) }
sync.waitGroup并发 实际使用中用的最多的还是这个并发方式,用起来很方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 var wg sync.WaitGroup worker := make (chan struct {}, 10 ) count, _ := GetTop10kCount() var i int64 for i = 1 ; i < count; i++ { worker <- struct {}{} wg.Add(1 ) imaven := model.Imaven{Id: i} go func (m model.Imaven) { defer func () { <-worker wg.Done() }() _, err := global.Top10kEngine.Table("i_maven" ).Get(&imaven) if err != nil { panic (err) } if has, _ := global.Engine.Exist(&model.Jarhash{ GroupId: imaven.GroupId, ArtifactId: imaven.ArtifactId, Version: imaven.Version, }); has { fmt.Println(imaven.GroupId, imaven.ArtifactId, imaven.Version, " is exists" ) return } else { WriteJarHash2DB(global.Client.R(), imaven.GroupId, imaven.ArtifactId, imaven.Version) } }(imaven) } wg.Wait()
测试 测试文件以name_test.go
命名
1 2 3 4 5 import "testing" func TestAccount (t *testing.T) {}
go test -v
检测是否通过测试
第三方模块推荐 gin框架 https://geektutu.com/post/quick-go-gin.html
https://laravelacademy.org/post/21861
xorm 文档:
https://gitea.com/xorm/xorm/src/branch/master/README_CN.md
gotoml 用来读配置文件的第三方包
第三方包”github.com/pelletier/go-toml”
go get 一下然后import
假设有配置文件config.toml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 path, err := filepath.Abs("config.toml" ) if err != nil { panic (err) } config, err := toml.LoadFile(path) if err != nil { panic (err) } var none bool db_host := config.Get("db.host" ) if v, ok := db_host.(string ); ok { dbHOST = v } else { none = true } if none { panic ("the config is not complete" ) }
resty 用于发送和处理http请求的
https://github.com/go-resty/resty
gjson 用于处理json数据的
https://github.com/tidwall/gjson