目录
基础
example 1
package main
import (
"fmt"
"math"
)
func Sqrt(x float64) float64 {
z:=1.0
for d:=math.Abs(x-z*z);d>0.00001;{
z -= (z*z - x) / (2*z)
d=math.Abs(x-z*z)
fmt.Println(z)
}
return z
}
func main() {
fmt.Println(Sqrt(2))
}
切片 capacity
- 对数组或切片执行array[start:end]操作生成切片时,切片的capacity总等于源数组/源切片的capacity减去start的值,比如array原本的capacity为4,s0 := array[1:],则s0的capacity为3。
- 切片s的capacity等于数组primes的长度。当我们给切片一次次append元素,直到切片的长度大于capacity时,capacity的值会自动更新,而且是简单的updatedCapacity = capacity X 2
example 2
package main
import "golang.org/x/tour/pic"
func Pic(dx, dy int) [][]uint8 {
pic :=make([][]uint8, dy)
for i := range pic {
pic[i]=make([]uint8,dx)
for j := range pic[i]{
pic[i][j]=uint8(i*j)
}
}
return pic
}
func main() {
pic.Show(Pic)
}
example 3
package main
import (
"strings"
"golang.org/x/tour/wc"
)
func WordCount(s string) map[string]int {
words := strings.Fields(s)
m := make(map[string]int)
for _, v := range words {
m[v] = m[v]+1
}
return m
}
func main() {
wc.Test(WordCount)
}
reciever
type BasicEvent struct {
EventId int
}
func (ev *BasicEvent) updateEventID(id int) {
ev.EventId = id
}
这里ev就是一个reciever.
一个指针reciever可以接受一个值reciever,反之亦可。
不管调用的时候传给reciever是个值还是指针,只要是方法的receiver是值类型,都无法改变调用者的内部状态。
如果receiver是指针,内部状态才可以改. 这个过程叫做自动解引用和取引用
interface
type Namer interface {
Method1(param_list) return_type //方法名(参数列表) 返回值列表
Method2(param_list) return_type //方法名(参数列表) 返回值列表
}
一个接口被赋值后,他的type属性是实现了这个接口的类的类型
判断接口i的实际类型,type为关键字:
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21)
do("hello")
do(true)
}
由于reciever既可以是指针也可以是值,传给interface的是既可以是指针也可以是引用,后者可以通过interface修改引用对象的内容
example 4
package main
import "fmt"
type IPAddr [4]byte
// TODO: Add a "String() string" method to IPAddr.
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
func (ad IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", ad[0],ad[1],ad[2],ad[3])
}
example 5
package main
import (
"fmt"
"math"
)
type ErrNegativeSqrt float64
func Sqrt(x float64) (float64, error) {
if x>=0 {
z:=1.0
for d:=math.Abs(x-z*z);d>0.00001;{
z -= (z*z - x) / (2*z)
d=math.Abs(x-z*z)
}
return z, nil
}
fmt.Println("asd")
return x, ErrNegativeSqrt(x)
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
//这里必须吧e转换成float,不然会循环打印错误
}
example 6
package main
import "golang.org/x/tour/pic"
import "image"
import "image/color"
type Image struct {
pic [][]uint8
}
func main() {
m := &Image{Pic(100, 100)} // 这里一定要用指针
pic.ShowImage(m)
}
func (img *Image) Bounds() image.Rectangle {
return image.Rect(0, 0, 100, 100)
}
func (img *Image) At(x, y int) color.Color {
return color.RGBA{img.pic[x][y], img.pic[x][y], 255, 255}
}
func (i *Image) ColorModel() color.Model {
return color.RGBAModel
}
func Pic(dx, dy int) [][]uint8 {
pic := make([][]uint8, dy)
for i := range pic {
pic[i] = make([]uint8, dx)
for j := range pic[i] {
pic[i][j] = uint8(i * j)
}
}
return pic
}
example 7
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mu.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mu.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
package
一个文件夹下只能有一个package。import后面的其实是GOPATH开始的相对目录路径,包括最后一段。但由于一个目录下只能有一个package,所以import一个路径就等于是import了这个路径下的包。注意,这里指的是“直接包含”的go文件。如果有子目录,那么子目录的父目录是完全两个包。比如你实现了一个计算器package,名叫calc,位于calc目录下;但又想给别人一个使用范例,于是在calc下可以建个example子目录(calc/example/),这个子目录里有个example.go(calc/example/example.go)。此时,example.go可以是main包,里面还可以有个main函数。一个package的文件不能在多个文件夹下。如果多个文件夹下有重名的package,它们其实是彼此无关的package。如果一个go文件需要同时使用不同目录下的同名package,需要在import这些目录时为每个目录指定一个package的别名。
channels
By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
import “fmt”
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
channel 之间拷贝的是引用
包控制 module
在Go 1.13中,我们可以通过GOPROXY来控制代理,以及通过GOPRIVATE控制私有库不走代理。
设置GOPROXY代理:
go env -w GOPROXY=https://goproxy.cn,direct
设置GOPRIVATE来跳过私有库,比如常用的Gitlab或Gitee,中间使用逗号分隔:
go env -w GOPRIVATE=*.gitlab.com,*.gitee.com
如果在运行go mod vendor时,提示Get https://sum.golang.org/lookup/xxxxxx: dial tcp 216.58.200.49:443: i/o timeout,则是因为Go 1.13设置了默认的GOSUMDB=sum.golang.org,这个网站是被墙了的,用于验证包的有效性,可以通过如下命令关闭:
go env -w GOSUMDB=off
私有仓库自动忽略验证
可以设置 GOSUMDB=“sum.golang.google.cn”, 这个是专门为国内提供的sum 验证服务。
go env -w GOSUMDB="sum.golang.google.cn"
-w 标记 要求一个或多个形式为 NAME=VALUE 的参数, 并且覆盖默认的设置
在goland中使用mod
go mod 会把依赖下载到$GOPATH路径下
设置GOPATH后,工程中使用import的根目录是GOPATH中的src目录
不同的项目可以制定不同的gopath
- go mod 使用
进入项目文件夹后go mod init 包名
会自动创建go.mod文件
之后buld或run的时候就会自动下载外部包(或者手动通过 go get 包名下载),并把依赖自动记录到go.mod 中。 go get 下载的包会保存在gopath中。
build install run
build 生成可执行文件到当前目录
install 如果有入口函数,生成可执行到gopath/bin
如果只是.a的归档文件(等待调用)放到gopath/pkg下
run 直接运行,不生成可执行
一个文件夹里只能有一个main函数
相对路径
最终的代码目录结构是这样的。
gee/
|--gee.go
|--go.mod
main.go
go.mod
day1-http-base/base3/go.mod
module example
go 1.13
require gee v0.0.0
replace gee => ./gee
在 go.mod 中使用 replace 将 gee 指向 ./gee
从 go 1.11 版本开始,引用相对路径的 package 需要使用上述方式
- 相对路径导包
import “./test_model” //当前文件同一目录的test_model目录,但是不建议这种方式import
- 绝对路径导包
import “mygoproject/test_model” //加载GOPATH/src/mygoproject/test_model模块
panic,recover , trace
发生或调用panic 后,程序会从调用 panic的函数位置或发生panic 的地方立即返回,逐层向上执行函数的defer语句, 然后逐层打印函数调用堆栈,直到被 recover 捕获或运行到最外层函数而退出。 ! panic的参数是一个空接口类型 interface{},所以任意类型的变量都可以传递给 panic(xxx) !
recover可以恢复错误,有点类似于try except
func test_recover() {
defer func() {
fmt.Println("defer func")
if err := recover(); err != nil {
fmt.Println("recover success")
}
}()
arr := []int{1, 2, 3}
fmt.Println(arr[4])
fmt.Println("after panic")
}
func main() {
test_recover()
fmt.Println("after recover")
}
一下函数打印当前函数栈,并跳过前三个
func trace(message string) string {
var pcs [32]uintptr
n := runtime.Callers(3, pcs[:]) // skip first 3 caller
var str strings.Builder
str.WriteString(message + "\nTraceback:")
for _, pc := range pcs[:n] {
fn := runtime.FuncForPC(pc)
file, line := fn.FileLine(pc)
str.WriteString(fmt.Sprintf("\n\t%s:%d", file, line))
}
return str.String()
}
list
list的使用
1、 通过container/list包的New方法声明list
•变量名:=list.New()
2、通过var声明list
• var 变量名 list.List
• list与切片和map不同,没有具体元素类型的限制。list中的元素可以是任意类型。
•在CPP里面,list的成员必须是同一个数据类型,但是Go语言中却允许list中插入任意类型的成员。
•建议使用New()实现声明list
3、list.element
Element类型包含几个包级私有字段,分别用于存储前一个元素、后一个元素以及所属链表的指针值;还有一个Value公开字段,
该字段代表了持有元素的实际值,也是interface{}类型。在Element类型的零值中,这些字段的默认值都是nil。
接口的函数型实现
type Getter interface {
Get(key string) ([]byte, error)
}
// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)
// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {
return f(key)
}
真正调用时:
func GetFromSource(getter Getter, key string) []byte {
buf, err := getter.Get(key)
if err == nil {
return buf
}
return nil
}
func test(key string) ([]byte, error) {
return []byte(key), nil
}
func main() {
GetFromSource(GetterFunc(test), "hello")
}
方便直接传入普通函数或匿名函数
debug
安装dlv
go get -u github.com/derekparker/delve/cmd/dlv
attach到进程PID
dlv attach <pid>
下划线
import 下划线(如:import _ hello/imp)的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import _ 引用该包。即使用【import _ 包路径】只是引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。
变量前加下划线,用于判断是否实现某个接口,例如
package main
type I interface {
Sing()
}
type T struct {
}
func (t T) Sing() {
}
// 编译通过
func main(){
var _ I = T{}
var _ I = &T{}
}
类型断言
当函数作为参数并且被调用函数将参数类型指定为interface{}的时候是没有办法直接调用该方法的
func ServeHTTP(s string) {
fmt.Println(s)
}
type Handler func(string)
func panduan(in interface{}) {
in.(Handler)("wujunbin") //必须这样写,不然报错
}
func main() {
panduan(Handler(ServeHTTP))
}
反射
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
t := reflect.TypeOf(q)
k := t.Kind()
fmt.Println("Type ", t)
fmt.Println("Kind ", k)
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
反射应用于空接口
Type 表示 interface{} 的实际类型(在这里是 main.Order),而 Kind 表示该类型的特定类别(在这里是 struct)。
reflect.Type 表示 interface{} 的具体类型,而 reflect.Value 表示它的具体值。reflect.TypeOf() 和 reflect.ValueOf() 两个函数可以分别返回 reflect.Type 和 reflect.Value。
NumField() 方法返回结构体中字段的数量,而 Field(i int) 方法返回字段 i 的 reflect.Value
反射和接口配合实现序列化和反序列化
type P struct {
X, Y, Z int
Name string
}
func main() {
// 初始化 encoder 和 decoder
var buf bytes.Buffer
encoder := gob.NewEncoder(&buf) // will write to buf
decoder := gob.NewDecoder(&buf)
err1 := encoder.Encode(P{X: 3, Y: 4, Z: 5, Name: "hello"})
err2 := encoder.Encode(P{X: 6, Y: 7, Z: 8, Name: "hello2"})
if err1 != nil {
log.Fatal("Encode error:", err1)
}
if err2 != nil {
log.Fatal("Encode error:", err2)
}
var p1,p2 P
err3 := decoder.Decode(&p1)
decoder.Decode(&p2)
if err3 != nil {
log.Fatal("Decode error:", err3)
}
p3 := reflect.New(reflect.TypeOf(p2))
p4 :=p3.Interface().(*P)
fmt.Printf("%d\n", p4.Y)
}
注意当这里如果type类型是map,需要使用专有的new函数,比如:
p3 := reflect.MakeMap(reflect.TypeOf(p2))
p4 := (n_m.Interface().(map[string]int))
slice 则用MakeSlice
Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作 类似的方法还有reflect.Indirect
不能直接通过指针的反射对象来修改对象的内容,必须先通过Elem转换成对象本身的反射对象。
但是new出来的value和makemap出来的value可以相互转换:
m := map[string]int{"A": 1, "B": 2}
replyv := reflect.New(reflect.TypeOf(m))
n_m := reflect.MakeMap(reflect.TypeOf(m))
replyv.Elem().Set(n_m)
n := replyv.Elem().Interface().(map[string]int)
n["2"] = 2
fmt.Println(n)
type T struct {
}
func (s T) G1(Arg string) { //可以接受任意个string参数
}
reflect.TypeOf(t).Elem().method(0)输出为
{G1 func(main.T, string) <func(main.T, string) Value> 0}
reflect.TypeOf(t).method(0)输出为
{G1 func(*main.T, string) <func(*main.T, string) Value> 0}
- 通过反射修改变量
修改的对象必须是指针类型, Elem()返回的类型是reflect.value
package main
import (
"reflect"
"fmt"
)
type Student struct {
Id int
Name string
}
func main() {
s := &Student{Id: 1, Name: "咖啡色的羊驼"}
v := reflect.ValueOf(s)
// 修改值必须是指针类型否则不可行
if v.Kind() != reflect.Ptr {
fmt.Println("不是指针类型,没法进行修改操作")
return
}
// 获取指针所指向的元素
v = v.Elem()
// 获取目标key的Value的封装
name := v.FieldByName("Name")
if name.Kind() == reflect.String {
name.SetString("小学生")
}
fmt.Printf("%#v \n", *s)
// 如果是整型的话
test := 888
testV := reflect.ValueOf(&test)
testV.Elem().SetInt(666)
fmt.Println(test)
}
导出
在 Go语言中,如果想在一个包里引用另外一个包里的标识符(如类型、变量、常量等)时,必须首先将被引用的标识符导出,将要导出的标识符的首字母大写就可以让引用者可以访问这些标识符了。
defer 调用时机
包裹defer的函数返回时
包裹defer的函数执行到末尾时
所在的goroutine发生panic时
一个例子:
func test() (int,error) {
defer func() {
fmt.Println("defer")
}()
return fmt.Println("return")
}
func main(){
test()
}
select
select语句选择一组可能的send操作和receive操作去处理。它类似switch,但是只是用来处理通讯(communication)操作。
它的case可以是send语句,也可以是receive语句,亦或者default。
每个 case 都必须是一个通信
所有 channel 表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通信可以进行,它就执行,其他被忽略。
如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
否则:
如果有 default 子句,则执行该语句。
如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。
最多允许有一个default case,它可以放在case列表的任何位置,尽管我们大部分会将它放在最后。
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
上面这个程序中协程负责接收主程序中fiboacci不断产生的数据,当接收完十个数据后,协程给fiboacci发送退出信号。
三个点 ‘…’
func test1(args ...string) { //可以接受任意个string参数
for _, v:= range args{
fmt.Println(v)
}
}
func main(){
var strss= []string{
"qwr",
"234",
"yui",
"cvbc",
}
test1(strss...) //切片被打散传入
}
使用…用于参数数量不变的问题
func (t T) Sing(Args string, B ...T ) {
for _, v:= range Args{
fmt.Println(v)
}
}
使用…的变量在反射的时候算一个变量,但不显示变量名,上面的例子包括实例本身总共有3个参数
nil
type Abc interface{
String() string
}
// 类型
type Efg struct{
data string
}
// 类型Efg实现Abc接口
func (e Efg)String()string{
return e.data
}
// 获取一个*Efg实例
func GetEfg() *Efg{
return new(Efg)
}
// 比较
func CheckAE(a Abc) bool{
fmt.Println(reflect.ValueOf(a))
return a == nil
}
func main() {
var efg *Efg
efg = GetEfg()
b := CheckAE(*efg)
fmt.Println(b)
此时打印false
若改成
func main() {
// var efg *Efg
//efg = GetEfg()
b := CheckAE(nil)
fmt.Println(b)
}
则打印true
waitgroup
WaitGroup对象内部有个计时器, 最初从0 开始, 他有3个方法 Add() , Done(), Wait()用来控制计数器的数量。 Add(n) 把计数器设置成n, Done() 每次把计数器-1, wait() 会阻塞代码的运行, 直到计数器的值减为0
make
a :make([]int, 2, 4)
2是当前长度,4是预留空间
即len(a)=2,cap(a)=4。
range
在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
reflect.DeepEqual
m1:=map[string]int{"a":1,"b":2,"c":3};
m2:=map[string]int{"a":1,"c":3,"b":2};
由于go中map没有重载==,map只能和nil比较,但是可以用reflect.DeepEqual(m1,m2)判断m1和m2的值是否相等,在这里判断的结果是true
组合
组合的方法集
组合结构的方法集有如下规则:
若类型 S 包含匿名字段 T,则 S 的方法集包含 T 的方法集。
若类型 S 包含匿名字段 *T,则 S 的方法集包含 T 和 T 方法集。
不管类型 S 中嵌入的匿名字段是 T 还是T,*S 方法集总是包含 T 和 *T 方法集。
type Animal struct {
Name string
}
func (a *Animal) Eat() {
fmt.Printf("%v is eating", a.Name)
fmt.Println()
}
type Cat struct {
*Animal
}
cat := &Cat{
Animal: &Animal{
Name: "cat",
},
}
cat.Eat() // cat is eating
接口之间也可以组合
在Go语言中,可以在接口A中组合其它的一个或多个接口(如接口B、C),这种方式等价于在接口A中添加接口B、C中声明的方法。
值传递
函数调用默认都是值传递
type Cache struct {
maxBytes int64
nbytes int64
}
func test(i Cache){
fmt.Printf("test======%p",&(i.maxBytes))
}
func main() {
i:=Cache{maxBytes: 1}
test(i)
fmt.Printf("main======%p",&(i.maxBytes))
}
test======0xc0000aa2a0
main======0xc0000aa290
slice虽然也是值传递,但是slice内部会保持一个真实数组的指针,也会被复制到形参中,所以只要不发生扩充,但是在函数内部的修改也可能会反映到函数外部
当形参是一个接口时,既可以传入指针也可以传入值
string
-
Fields
Fields 以连续的空白字符为分隔符,将 s 切分成多个子串,结果中不包含空白字符本身 -
Builder
package main
import (
"fmt"
"strings"
)
func main() {
ss := []string{
"sh",
"hn",
"test",
}
var b strings.Builder
for _, s := range ss {
fmt.Fprint(&b, s)
}
print(b.String())
}
builder可以减少一半string 拼接时频繁创建和销毁的开销。
- WriteString
向builder中写入字符串 - Reset
清空buider