golang go简介
golang的优势:
编译型语言 :Go是编译型语言,静态链接,运行时可不依赖库。(1.5版本后支持动态链接)
语言层并发: 基于CSP(Communicating Sequential Processes)并发模型 ,提供了轻量级线程(goroutines)和通道(channels)机制,使得并发编程更加高效和简洁。
简单易学: 支持内嵌c语言,关键词少,面向对象,跨平台
缺点:
包管理,大部分包都在github上
所有Exception都由Error处理
缺少泛型 :在Go 1.18版本之前,Go 1.18引入了泛型。
C语言更接近硬件,提供了更多的底层控制,而Go语言则在保证性能的同时,更注重开发效率和代码的安全性。当需要在Go中使用C代码时,通常需要通过cgo进行桥接。
go编译运行
1 2 3 4 go run my.go //编译运行 go build my.go //编译 ./my //运行
go生态
Web:beego、gin、echo、iris
微服务:go kit、 istio
容器编排:k8s、swarm
服务发现:consul
存储引擎:etcd(k/v存储)、tidb(分布式存储)
静态建站:hugo
中间件:nsq(消息队列)、zinx(tcp长连接框架,轻量级服务器)、Leaf(游戏服务器)、gPRC(RPC框架)、codis(redis集群)
爬虫框架: go query
go路径与导包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" "golangstudy/lib1" ) func main () { fmt.Println("Hello from main" ) lib1.Hello() } package lib1import "fmt" func Hello () { fmt.Println("Hello from lib1" ) }
1 go env -w GO111MODULE=off
1 2 3 4 . ├── lib1 │ └── lib1.go └── my.go
go 方法名大小写
点、匿名和别名导包 1 2 3 4 5 import ( _ "golangstudy/lib1" mylib "golangstudy/lib1" . "golangstudy/lib1" )
GOMODULES
GOPATH
bin 存储编译生成的可执行文件。
pkg存储编译生成的包文件。
src :存储 Go 源代码文件。
GOPATH缺点:
无版本控制
无法同步一致第三方版本号
无法指定当前项目引用的第三方版本号
GOMODOULES
1 go env -w GO111MODULE=on
GOPROXY:修改为国内镜像,direct指示符用于指示go回到模块版本的源地址抓取
GOSUMBDB:检验拉取的模块数据,通过GOPROXY修改代理解决
GOPRIVATE(作为GONOPROXY和GONOSUMDB默认值):用于指示私有模块,可通过通配符设置
初始化gomod并为项目命名
GOPATH/pkg/mod :用于存储下载的模块版本,以供本地项目使用。
GOSUM文件
go.sum文件包含以下格式的条目:
1 <module> <version> <hash>
<module>:依赖模块的路径。
<version>:依赖模块的版本号。
<hash>:依赖模块版本文件的哈希值。通常是两个哈希值,一个是校验和(checksum),另一个是用于验证模块内容的哈希。
helloworld go的包名类似于cpp的命名空间,同一包名的代码将在编译时合并,包名要求:
包名应该是一个小写单词。
包名通常与其所在的目录名称相同。
1 2 3 4 5 6 7 8 9 10 package main import ( "fmt" ) func main () { fmt.Println("hello world" ) }
变量声明与格式化字符串 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 package main import ( "fmt" ) func main () { var a int var b string = "Randolfluo" var c = 2 d := 3.3 var ( e int = 3 f int = 4 ) fmt.Printf("a=%d,b=%s,c=%d,d=%f,e=%d,f=%d\n" , a, b, c, d, e, f) fmt.Printf("a=%T,b=%T,c=%T,d=%T,e=%T,f=%T\n" , a, b, c, d, e, f) }
golang常用的格式化字符串
%T:输出值的类型。
%t:布尔值。
%d:整型十进制表示。
%x, %X:整型十六进制表示。
%f, %F:浮点数。
%s:字符串。
%q:带双引号的字符串或字符。
%p:指针。
%v:表示任意类型。
const 与 iota
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package main import ( "fmt" ) const ( a, b = iota + 1 , iota + 2 c, d e, f = iota + 2 , iota + 2 ) func main () { fmt.Println(a, b, c, d, e, f) }
多返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package main import ( "fmt" ) func add_dsc (a int , b int ) (add int , dsc int ){ add = a + b dsc = a - b return } func main () { add, dsc := add_dsc(1 , 2 ) fmt.Println(add, dsc) return }
defer 类似于cpp的析构函数,用于返回时释放资源。
其执行顺序类似于栈
defer 语句会在 return 之后执行。
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 funcA () int { defer fmt.Println("A" ) return 0 } func funcB () int { defer fmt.Println("C" ) defer fmt.Println("B" ) return funcA() } func main () { funcB() }
数组&切片slice 数组
长度固定。
类型相同。
在内存中连续分配。
作为值类型,传递时会复制整个数组。
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" ) func change_array (arr [10]int ) { arr[0 ] = 1144514 } func main () { var Array1 [10 ]int for i := 0 ; i < len (Array1); i++ { fmt.Print(Array1[i], " " ) } fmt.Println() var Array2 [10 ]int = [10 ]int {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } for idx, val := range Array2 { fmt.Printf("Array2[%d] = %d\n" , idx, val) } change_array(Array2) fmt.Printf("Array2[0] = %d\n" , Array2[0 ]) }
切片slice
长度动态。
类型相同。
底层通常由数组支持。
作为引用类型,传递时不会复制底层数组(复制指针)。
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 package mainimport ( "fmt" ) func change_slice1ay (slice1 []int ) { slice1[0 ] = 1144514 } func main () { var slice0 []int if slice0 == nil { fmt.Println("slice0 is nil" ) } else { fmt.Println("slice0 is not nil" ) } slice1 := []int {1 , 2 , 3 , 4 , 5 } change_slice1ay(slice1) fmt.Println(slice1) slice3 := make ([]int , 5 , 10 ) fmt.Println(slice3) fmt.Println(len (slice3), cap (slice3), slice3) slice3 = append (slice3, 1 , 2 , 3 , 4 , 5 , 6 ) fmt.Println(len (slice3), cap (slice3), slice3) slice4 := []int {1 , 2 , 3 , 4 , 5 } slice5 := slice4[0 :3 ] slice5[0 ] = 100 fmt.Println(slice4) fmt.Println(slice5) slice6 := make ([]int , 5 , 10 ) copy (slice6, slice4) slice6[0 ] = 114514 fmt.Println(slice4) fmt.Println(slice6) }
map 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ( "fmt" ) func changemap (m map [int ]string ) { for k, _ := range m { m[k] = "Randolfluo" } } func main () { var map1 map [int ]string map1 = make (map [int ]string ) map1[0 ] = "hello" fmt.Println(map1) map2 := make (map [int ]string ) map2[0 ] = "Randolfluo" fmt.Println(map2) map3 := map [int ]string { 0 : "Randolfluo" , 1 : "Hatsune Miku" , } fmt.Println(map3) map3[2 ] = "114514" fmt.Println(map3) delete (map3, 0 ) fmt.Println(map3) map3[2 ] = "1919810" fmt.Println(map3) changemap(map3) fmt.Println(map3) }
type
定于的类型可以基于已有的类型,但它被视为一个完全不同的类型。
可以与struct、interface等关键词一起使用定义结构体
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package main import ( "fmt" ) type MyInt int type Person struct { name string age int } func changePerson1 (p Person) { p.name = "Hatsune Miku" p.age = 16 } func changePerson2 (p *Person) { p.name = "Hatsune Miku" p.age = 16 } func main () { var a MyInt = 1 fmt.Printf ("a = %T\n" , a) var p Person p.age = 20 p.name = "Randolfluo" fmt.Println (p) changePerson1 (p) fmt.Println (p) changePerson2 (&p) fmt.Println (p) }
面向对象 封装 定义一个结构体存储数据(),然后通过公开的方法传入结构体(指针)来访问修改这些字段。
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 package mainimport "fmt" type Person struct { name string age int } func (p Person) Get() (name string , age int ) { age = p.age name = p.name return } func (p *Person) Set(name string , age int ) { p.name = name p.age = age } func main () { p := Person{name: "tom" , age: 18 } name, age := p.Get() fmt.Println(name, age) p.Set("jack" , 20 ) name, age = p.Get() fmt.Println(name, age) }
继承 组合:组合是一种将一个类型嵌入到另一个类型中的方法,这样内嵌的类型就可以被外部类型使用。
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 package mainimport "fmt" type Person struct { name string age int } func (p *Person) showinfo() (name string , age int ) { name = p.name age = p.age return } type Student struct { Person school string } func (s *Student) showinfo() (name string , age int , school string ) { name = s.name age = s.age school = s.school return } func main () { var s Student s.name = "Randolfluo" s.age = 20 s.school = "zjxu" name, age, school := s.showinfo() fmt.Println(name, age, school) }
多态 鸭子类型: 一个对象是否符合某个接口,不是由它的类型决定,而是由它实现了哪些方法决定。一个类型只要实现了接口中定义的所有方法,那么这个类型的对象就可以赋值给这个接口类型的变量,而无需显式地声明实现了这个接口。
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 package mainimport "fmt" type Animal interface { Sleep() GetColor() string Fly() } func (c *Cat) Sleep() { fmt.Println("Cat sleep" ) } func (b *Bird) Sleep() { fmt.Println("Bird sleep" ) } func (c *Cat) Fly() { fmt.Println("Cat can't fly" ) } func (b *Bird) Fly() { fmt.Println("Bird flies" ) } type Cat struct { Color string } func (c *Cat) GetColor() string { return c.Color } type Bird struct { Color string } func (b *Bird) GetColor() string { return b.Color } func showAnimal (a Animal) { a.Sleep() fmt.Println("Animal color:" , a.GetColor()) a.Fly() } func main () { var an Animal an = &Cat{"Black" } showAnimal(an) cat := Cat{Color: "Black" } bird := Bird{Color: "White" } showAnimal(&cat) showAnimal(&bird) }
interface空接口 interface{} 是一个空接口,它表示没有任何方法。由于任何类型都至少实现了零个方法,因此任何类型都实现了空接口 interface{}。这意味着 interface{} 可以被任何类型的值赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func Isstring (a interface {}) { value, ok := a.(string ) if ok { fmt.Println(value, " is string" ) } else { fmt.Println(a, " is not string" ) } } func main () { Isstring(123 ) Isstring("Randolfluo" ) }
在Go 1.18及更高版本中,推荐使用 any 来代替 interface{},特别是在使用泛型时。
pair
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 package mainimport ( "fmt" "io" "os" ) func main () { tty, err := os.OpenFile("/dev/tty" , os.O_RDWR, 0 ) if err != nil { fmt.Println("Error opening /dev/tty:" , err) return } var r io.Reader r = tty var w io.Writer w = r.(io.Writer) w.Write([]byte ("Hello, World!\n" )) err = tty.Close() if err != nil { fmt.Println("Error closing /dev/tty:" , err) } }
reflect 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 package mainimport ( "fmt" "reflect" ) type Person struct { Name string Age int } func (p Person) String() string { return fmt.Sprintf("Person: %s %d" , p.Name, p.Age) } func main () { user := Person{ Name: "Randolfluo" , Age: 20 , } DoFieldAndMethod(user) } func DoFieldAndMethod (input interface {}) { inputtype := reflect.TypeOf(input) inputvalue := reflect.ValueOf(input) fmt.Println("type:" , inputtype.Name(), "value:" , inputvalue) for i := 0 ; i < inputtype.NumField(); i++ { field := inputtype.Field(i) value := inputvalue.Field(i).Interface() fmt.Printf("%s: %v = %v\n" , field.Name, field.Type, value) } for i := 0 ; i < inputtype.NumMethod(); i++ { method := inputtype.Method(i) fmt.Printf("%s: %v\n" , method.Name, method.Type) } }
结构体标签 结构体标签可以为结构体的字段添加元数据。标签通常用于编码和解码(如 JSON、XML),验证,数据库映射等。
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 58 59 60 61 62 63 64 package mainimport ( "encoding/json" "fmt" "reflect" ) type Person struct { Name string `info:"name"` Age int `info:"age"` } func findTag (t interface {}) { tmp := reflect.TypeOf(t).Elem() for i := 0 ; i < tmp.NumField(); i++ { taginfo := tmp.Field(i).Tag.Get("info" ) fmt.Printf("%s\n" , taginfo) } } type Student struct { Name string `json:"name"` Age int `json:"age"` Sex []string `json:"sex"` } func main () { var p Person findTag(&p) s := Student{ Name: "USA" , Age: 18 , Sex: []string {"male" , "female" , "tank" }, } jsonData, err := json.Marshal(s) if err != nil { fmt.Println("json err:" , err) } fmt.Println(string (jsonData)) s1 := Student{} err1 := json.Unmarshal(jsonData, &s1) if err1 != nil { fmt.Println("json err:" , err1) } fmt.Println(s1) }
Goroutine
协程是轻量级的线程,通常由用户程序通过协程库或语言层面的支持来实现。协程可以在单个线程内实现多任务的并发执行。
即在线程的层面上(用户态),再运行一个调度器进行协程调度
在 Go 中,当主 goroutine(即 main 函数)退出时,所有其他的 goroutine 也会立即退出。
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 package mainimport ( "fmt" "runtime" "time" ) func main () { defer fmt.Println("main exit" ) go func () { i := 0 defer fmt.Println("func exit" ) for { i++ fmt.Printf("%d " , i) time.Sleep(10 * time.Millisecond) if i == 500 { runtime.Goexit() } } }() time.Sleep(10 * time.Second) }
channel
channel不需要像文件需要经常关闭,当我们没有数据或显式结束range循环才去关闭channel
关闭channel后无法发送数据,但是可以接收数据
对于nil channel,收发都会被阻塞
channel的基本使用 我们可以将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 package mainimport ( "fmt" "time" ) func main () { c := make (chan int ) go func () { fmt.Print("Randolfluo\n" ) time.Sleep(2 * time.Second) c <- 0 }() num := <-c if num == 0 { fmt.Println("func exit with 0" ) } else { fmt.Println("func exit with others" ) } }
有容量的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 package mainimport "fmt" func channel_test (c chan int ) { defer close (c) for i := 0 ; i < 10 ; i++ { c <- i } } func main () { c := make (chan int , 3 ) go channel_test(c) fmt.Println("len cap i" ) for i := 0 ; i < 10 ; i++ { fmt.Println(len (c), " " , cap (c), " " , <-c) } }
range & select
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 package mainimport ( "fmt" "runtime" "time" ) func newTask (c, q chan int ) { i := 0 for { i++ c <- i time.Sleep(100 * time.Millisecond) if i == 20 { close (c) q <- 1 close (q) runtime.Goexit() } } } func main () { c := make (chan int ) q := make (chan int ) go newTask(c, q) label: for { select { case data := <-c: fmt.Print(data, " " ) case ret := <-q: fmt.Println("\nrange exit with code" , ret) break label } } fmt.Println("\nmain exit" ) }