视频学习链接:https://www.bilibili.com/video/BV1gf4y1r79E/?p=1&vd_source=21e06970a09504e227f7f3380f7cb09c
Golang学习笔记 Golang简介 Golang的优势
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package main import ( "fmt" "time" ) func goFunc (i int ) { fmt.Println("goroutine " , i, " ..." ) } func main () { for i := 0 ; i < 10000 ; i++ { go goFunc(i) } time.Sleep(time.Second) }
Golang适合做什么 (1)、云计算基础设施领域
代表项目:docker、kubernetes、etcd、consul、cloudflare CDN、七牛云存储等。
(2)、基础后端软件
代表项目:tidb、influxdb、cockroachdb等。
(3)、微服务
代表项目:go-kit、micro、monzo bank的typhon、bilibili等。
(4)、互联网基础设施
代表项目:以太坊、hyperledger等。
Golang明星作品
Golang的不足
包管理,大部分都在GitHub 上
无泛化类型(Golang 1.18+已经支持泛型)
所有Excepiton 都用Error 来处理(比较有争议)
对C 的降级处理,并非无缝,没有C 降级到asm 那么完美(序列化问题)
Golang语法 第一个Golang程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package main import ( "fmt" , "time" ) func main () { fmt.Println("Hello Go!" ) time.Sleep(1 * time.Second) }
终端运行
1 2 PS D:\Go_Project> go run hello.go Hello Go!
go run
表示 直接编译go语言并执行应用程序,一步完成
也可以先编译,再运行
1 PS D:\Go_Project> go build hello.go
此时在项目中生成可执行文件
1 2 PS D:\Go_Project> ./hello Hello Go!
第一行代码package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main
。package main
表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main
的包。
**import “fmt”**告诉 Go 编译器这个程序需要使用 fmt
包(的函数,或其他元素),fmt
包实现了格式化 IO(输入/输出)的函数。
func main()
是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init()
函数则会先执行该函数)。
注意:这里面go语言的语法,定义函数的时候,‘{’ 必须和函数名在同一行,不能另起一行。
/*...*/
是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 / 开头,并以 / 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
fmt.Println(...)
可以将字符串输出到控制台,并在最后自动增加换行字符 \n
。 使用 fmt.Print("hello, world\n")
可以得到相同的结果。 Print
和 Println
这两个函数也支持使用变量,如:fmt.Println(arr)
。如果没有特别指定,它们会以默认的打印格式将变量 arr
输出到控制台。
变量的声明 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 package mainimport ( "fmt" ) var gA int var gB int = 100 var gC = 200 func main () { var a int fmt.Println("a = " , a) fmt.Printf("type of a = %T\n" , a) var b int = 100 fmt.Println("b = " , b) fmt.Printf("type of b = %T\n" , b) var bb string = "abc" fmt.Printf("bb = %s, type of bb = %T\n" , bb, bb) var c = 100 fmt.Println("c = " , c) fmt.Printf("type of c = %T\n" , c) var cc = "abc" fmt.Printf("cc = %s, type of cc = %T\n" , cc, cc) d := 100 fmt.Println("d = " , d) fmt.Printf("type of d = %T\n" , d) e := "abc" fmt.Println("e = " , e) fmt.Printf("type of e = %T\n" , e) pi := 3.14 fmt.Println("pi = " , pi) fmt.Printf("type of pi = %T\n" , pi) fmt.Println("===============================" ) fmt.Println("gA = " , gA, ", gB = " , gB, ",gC = " , gC) var xx, yy int = 100 , 200 fmt.Println("xx = " , xx, ", yy = " , yy) var kk, ll = 100 , "byu_rself" fmt.Println("kk = " , kk, ", ll = " , ll) var ( vv int = 100 jj bool = true ) fmt.Println("vv = " , vv, ", jj = " , jj) }
常量 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" const ( BEIJING = iota SHANGHAI NINGBO ) const ( a, b = iota + 1 , iota + 2 c, d e, f g, h = iota * 2 , iota * 3 i, k ) func main () { const length int = 10 fmt.Println("length = " , length) fmt.Println("BEIJING = " , BEIJING) fmt.Println("SHANGHAI = " , SHANGHAI) fmt.Println("NINGBO = " , NINGBO) }
输出
length = 10 BEIJING = 0 SHANGHAI = 1 NINGBO = 2
将iota
乘$10$后
1 2 3 4 5 6 const ( BEIJING = 10 * iota SHANGHAI NINGBO )
输出
length = 10 BEIJING = 0 SHANGHAI = 10 NINGBO = 20
注意iota“每行”的概念
1 2 3 4 5 6 7 const ( a, b = iota + 1 , iota + 2 c, d e, f g, h = iota * 2 , iota * 3 i, k )
总结
const
为只读属性,可以用const
来定义枚举 类型
iota
只能配合const()
一起使用
每行 的iota
都会累加1,第一行的iota
的默认值是0
函数 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 57 58 59 60 package mainimport "fmt" func f1 (a string , b int ) int { fmt.Println("a = " , a) fmt.Println("b = " , b) c := 100 return c } func f2 (a string , b int ) (int , int ) { fmt.Println("a = " , a) fmt.Println("b = " , b) return 111 , 222 } func f3 (a string , b int ) (r1 int , r2 int ) { fmt.Println("a = " , a) fmt.Println("b = " , b) fmt.Println("赋值之前: r1 = " , r1, ", r2 = " , r2) r1 = 1000 r2 = 2000 return } func f4 (a string , b int ) (r1, r2 int ) { fmt.Println("a = " , a) fmt.Println("b = " , b) r1 = 1000 r2 = 2000 return } func main () { c := f1("abc" , 123 ) fmt.Println("c = " , c) ret1, ret2 := f2("hello" , 666 ) fmt.Println("ret1 = " , ret1, ", ret2 = " , ret2) ret1, ret2 = f3("f3" , 333 ) fmt.Println("ret1 = " , ret1, ", ret2 = " , ret2) }
输出
a = abc b = 123 c = 100 a = hello b = 666 ret1 = 111 , ret2 = 222 a = f3 b = 333 赋值之前: r1 = 0 , r2 = 0 ret1 = 1000 , ret2 = 2000
init()函数与import
init
函数可在package main
中,可在其他package中,可在同一个package中出现多次。
main
函数只能在package main
中。
执行顺序
Golang里面有两个保留的函数:init函数 (能够应用于所有的package)和main函数 (只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。
虽然一个package里面可以写任意多个init函数 ,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数 。
Go程序会自动调用init()
和main()
,所以你不需要在任何地方调用这两个函数。每个package中的init函数 都是可选的,但package main
就必须包含一个main函数 。
程序的初始化和执行都起始于main包。
如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。
当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数 (如果有的话),依次类推。
等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数 (如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:
例子演示
Golang中,函数的首字母为大写 时,则方法为public 方法,首字母为小写 时,方法为private 方法。
lib1.go
1 2 3 4 5 6 7 8 9 10 11 package lib1import "fmt" func Lib1Test () { fmt.Println("lib1Test()..." ) } func init () { fmt.Println("lib1.init()..." ) }
lib2.go
1 2 3 4 5 6 7 8 9 10 11 package lib2import "fmt" func Lib2Test () { fmt.Println("lib2Test()..." ) } func init () { fmt.Println("lib2.init()..." ) }
main.go
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "5-init/lib1" "5-init/lib2" ) func main () { lib1.Lib1Test() lib2.Lib2Test() }
输出结果
1 2 3 4 lib1.init()... lib2.init()... lib1Test()... lib2Test()...
修改lib1.go
,使其导入lib2.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package lib1import ( _ "5-init/lib2" "fmt" ) func Lib1Test () { fmt.Println("lib1Test()..." ) } func init () { fmt.Println("lib1.init()..." ) }
同时修改main.go
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "5-init/lib1" ) func main () { lib1.Lib1Test() }
输出结果
1 2 3 lib2.init()... lib1.init()... lib1Test()...
分析:main函数需要导入lib1,而lib1又需要导入lib2,因此先加载lib2,执行lib2的init函数后,再加载lib1,执行lib1的init函数,最后在main函数中调用方法
导入的包不仅可以匿名,也可以取别名
1 2 3 4 5 6 7 8 9 package mainimport ( mylib1 "5-init/lib1" ) func main () { mylib1.Lib1Test() }
也可通过.
,使得调用方法时无需输入包名(不推荐,容易引起歧义)
1 2 3 4 5 6 7 8 9 package mainimport ( . "5-init/lib1" ) func main () { Lib1Test() }
指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func swap (a int , b int ) { var temp int = a a = b b = temp } func main () { var a int = 10 b := 20 swap(a, b) fmt.Println("a = " , a, ", b = " , b) }
输出
Go 语言的取地址符是 &
,放到一个变量前使用就会返回相应变量的内存地址。
1 2 3 4 5 6 7 8 package mainimport "fmt" func main () { var a int = 10 fmt.Printf("变量的地址: %x\n" , &a ) }
输出
引用传递 是指在调用函数时将实际参数的地址 传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
以经典的交换函数swap()
举例
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 package mainimport "fmt" func swap (a *int , b *int ) { temp := *a *a = *b *b = temp } func main () { var a int = 10 b := 20 swap(&a, &b) fmt.Println("a = " , a, ", b = " , b) }
输出
加强对指针 的理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { var a int = 10 var p *int = &a fmt.Println(&a) fmt.Println(p) pp := &p fmt.Println(&p) fmt.Println(pp) }
defer defer
语句被用于预定对一个函数的调用。可以把这类被defer语句调用的函数称为延迟函数 。
defer
作用:
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。
知识点一: defer的执行顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func Demo () { defer fmt.Println("1" ) defer fmt.Println("2" ) defer fmt.Println("3" ) defer fmt.Println("4" ) } func main () { Demo() }
输出
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" func main () { defer fmt.Println("main end 1" ) defer fmt.Println("main end 2" ) fmt.Println("main:hello go 1" ) fmt.Println("main:hello go 2" ) }
输出
1 2 3 4 main:hello go 1 main:hello go 2 main end 2 main end 1
知识点二: defer和return谁先谁后
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" func deferFunc () int { fmt.Println("defer func called..." ) return 0 } func returnFunc () int { fmt.Println("return func called..." ) return 0 } func returnAndDefer () int { defer deferFunc() return returnFunc() } func main () { returnAndDefer() }
输出
1 2 return func called...defer func called...
总结
return 之后的语句先执行,defer 之后的语句后执行
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 package mainimport "fmt" func printArray (array [4]int ) { for index, value := range array { fmt.Println("index = " , index, ", value = " , value) } array[0 ] = 111 } func main () { var array1 [10 ]int for i := 0 ; i < len (array1); i++ { fmt.Println(array1[i]) } array2 := [8 ]int {1 , 2 , 3 , 4 } for index, value := range array2 { fmt.Println("index = " , index, ", value = " , value) } fmt.Printf("array1 type = %T\n" , array1) fmt.Printf("array2 type = %T\n" , array2) array3 := [4 ]int {1 , 2 , 3 , 4 } printArray(array3) fmt.Println("==============================" ) for index, value := range array3 { fmt.Println("index = " , index, ", value = " , value) } }
输出
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 0 0 0 0 0 0 0 0 0 0 index = 0 , value = 1 index = 1 , value = 2 index = 2 , value = 3 index = 3 , value = 4 index = 4 , value = 0 index = 5 , value = 0 index = 6 , value = 0 index = 7 , value = 0 array1 type = [10]int array2 type = [8]int index = 0 , value = 1 index = 1 , value = 2 index = 2 , value = 3 index = 3 , value = 4 ============================== index = 0 , value = 1 index = 1 , value = 2 index = 2 , value = 3 index = 3 , value = 4
切片
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" func printArr (array []int ) { for _, value := range array { fmt.Println("value = " , value) } array[0 ] = 100 } func main () { array1 := []int {1 , 2 , 3 , 4 } fmt.Printf("array1 type = %T\n" , array1) printArr(array1) fmt.Println("=======================" ) for _, value := range array1 { fmt.Println("value = " , value) } }
输出
1 2 3 4 5 6 7 8 9 10 array1 type = []int value = 1 value = 2 value = 3 value = 4 ======================= value = 100 value = 2 value = 3 value = 4
总结
数组的长度是固定的
固定长度的数组在传参的时候,是严格匹配数组类型的
数组在传值的时候是值拷贝,无法修改其原本数组
slice(动态数组)在传参时是引用传递,且不同元素长度的动态数组的形参是一致的
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 main () { slice1 := []int {1 , 2 , 3 } fmt.Printf("len = %d, slice = %v\n" , len (slice1), slice1) var slice2 []int fmt.Printf("len = %d, slice = %v\n" , len (slice2), slice2) slice2 = make ([]int , 3 ) slice2[0 ] = 100 fmt.Printf("len = %d, slice = %v\n" , len (slice2), slice2) slice3 := make ([]int , 3 ) fmt.Printf("len = %d, slice = %v\n" , len (slice3), slice3) if slice3 == nil { fmt.Println("slice3是一个空切片" ) } else { fmt.Println("slice3是有空间的" ) } }
切片容量的追加
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 package mainimport "fmt" func main () { var numbers = make ([]int , 3 , 5 ) fmt.Printf("len = %d, cap = %d, slice = %v\n" , len (numbers), cap (numbers), numbers) numbers = append (numbers, 4 ) fmt.Printf("len = %d, cap = %d, slice = %v\n" , len (numbers), cap (numbers), numbers) numbers = append (numbers, 5 ) fmt.Printf("len = %d, cap = %d, slice = %v\n" , len (numbers), cap (numbers), numbers) numbers = append (numbers, 6 ) fmt.Printf("len = %d, cap = %d, slice = %v\n" , len (numbers), cap (numbers), numbers) fmt.Println("=========================================" ) var numbers2 = make ([]int , 3 ) fmt.Printf("len = %d, cap = %d, slice = %v\n" , len (numbers2), cap (numbers2), numbers2) numbers2 = append (numbers2, 1 ) fmt.Printf("len = %d, cap = %d, slice = %v\n" , len (numbers2), cap (numbers2), numbers2) }
输出
1 2 3 4 5 6 7 len = 3, cap = 5, slice = [0 0 0] len = 4, cap = 5, slice = [0 0 0 4] len = 5, cap = 5, slice = [0 0 0 4 5] len = 6, cap = 10, slice = [0 0 0 4 5 6] ========================================= len = 3, cap = 3, slice = [0 0 0] len = 4, cap = 6, slice = [0 0 0 1]
总结
切片的长度 和容量 不同,长度 表示左指针至右指针之间的距离,容量 表示左指针至底层数组末尾的距离
切片的扩容机制 :append
的时候,如果长度 增加后超过容量 ,则将容量 增加$2$倍
切片的截取
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 package mainimport "fmt" func main () { numbers := []int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 } printSlice(numbers) fmt.Println("numbers ==" , numbers) fmt.Println("numbers[1:4] ==" , numbers[1 :4 ]) fmt.Println("numbers[:3] ==" , numbers[:3 ]) fmt.Println("numbers[4:] ==" , numbers[4 :]) numbers1 := make ([]int ,0 ,5 ) printSlice(numbers1) number2 := numbers[:2 ] printSlice(number2) number3 := numbers[2 :5 ] printSlice(number3) } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) }
输出
1 2 3 4 5 6 7 8 len=9 cap =9 slice=[0 1 2 3 4 5 6 7 8] numbers == [0 1 2 3 4 5 6 7 8] numbers[1:4] == [1 2 3] numbers[:3] == [0 1 2] numbers[4:] == [4 5 6 7 8] len=0 cap =5 slice=[] len=2 cap =9 slice=[0 1] len=3 cap =7 slice=[2 3 4]
copy()函数
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 () { s := []int {1 , 2 , 3 } s1 := s[0 :2 ] fmt.Println(s1) s1[0 ] = 100 fmt.Println(s) fmt.Println(s1) s2 := make ([]int , 3 ) copy (s2, s) fmt.Println(s2) }
输出
1 2 3 4 [1 2] [100 2 3] [100 2] [100 2 3]
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 package mainimport "fmt" func main () { var myMap1 map [string ]string if myMap1 == nil { fmt.Println("myMap1是一个空map" ) } myMap1 = make (map [string ]string , 10 ) myMap1["one" ] = "java" myMap1["two" ] = "python" myMap1["three" ] = "c++" fmt.Println(myMap1) myMap2 := make (map [int ]string ) myMap2[1 ] = "java" myMap2[2 ] = "python" myMap2[3 ] = "c++" fmt.Println(myMap2) myMap3 := map [string ]string { "one" : "java" , "two" : "python" , "three" : "c++" , } fmt.Println(myMap3) }
输出
1 2 3 4 myMap1是一个空map map[one:java three:c++ two:python] map[1:java 2:python 3:c++] map[one:java three:c++ two:python]
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 package mainimport "fmt" func printMap (cityMap map [string ]string ) { for key, value := range cityMap { fmt.Println("key = " , key, ", value = " , value) } } func changeMap (cityMap map [string ]string ) { cityMap["England" ] = "London" } func main () { cityMap := make (map [string ]string ) cityMap["China" ] = "Beijing" cityMap["Japan" ] = "Tokyo" cityMap["USA" ] = "NewYork" for key, value := range cityMap { fmt.Println("key = " , key, ", value = " , value) } delete (cityMap, "China" ) cityMap["USA" ] = "DC" changeMap(cityMap) fmt.Println("=====================================" ) printMap(cityMap) }
输出
1 2 3 4 5 6 7 key = Japan , value = Tokyo key = USA , value = NewYork key = China , value = Beijing ===================================== key = USA , value = DC key = England , value = London key = Japan , value = Tokyo
面向对象 结构体
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 package mainimport "fmt" type myint int type Book struct { title string auth string } func changeBook (book Book) { book.auth = "666" } func changeBook2 (book *Book) { book.auth = "777" } func main () { var a myint = 10 fmt.Println("a = " , a) fmt.Printf("type of a = %T\n" , a) var book1 Book book1.title = "Golang" book1.auth = "zhangsan" fmt.Printf("%v\n" , book1) changeBook(book1) fmt.Printf("%v\n" , book1) changeBook2(&book1) fmt.Printf("%v\n" , book1) }
输出
1 2 3 4 5 a = 10 type of a = main.myint{Golang zhangsan} {Golang zhangsan} {Golang 777}
封装
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 package mainimport "fmt" type Hero struct { Name string Ad int Level int } func (this Hero) Show() { fmt.Println("Name = " , this.Name) fmt.Println("Ad = " , this.Ad) fmt.Println("Level = " , this.Level) } func (this Hero) GetName() string { return this.Name } func (this Hero) SetName(newName string ) { this.Name = newName } func main () { hero := Hero{Name: "zhangsan" , Ad: 100 , Level: 10 } hero.Show() hero.SetName("LiSi" ) hero.Show() }
输出
1 2 3 4 5 6 Name = zhangsan Ad = 100 Level = 10 Name = zhangsan Ad = 100 Level = 10
将传入的对象修改为指针
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" type Hero struct { Name string Ad int Level int } func (this *Hero) Show() { fmt.Println("Name = " , this.Name) fmt.Println("Ad = " , this.Ad) fmt.Println("Level = " , this.Level) } func (this *Hero) GetName() string { return this.Name } func (this *Hero) SetName(newName string ) { this.Name = newName } func main () { hero := Hero{Name: "zhangsan" , Ad: 100 , Level: 10 } hero.Show() hero.SetName("LiSi" ) hero.Show() }
输出
1 2 3 4 5 6 Name = zhangsan Ad = 100 Level = 10 Name = LiSi Ad = 100 Level = 10
继承
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 package mainimport "fmt" type Human struct { name string sex string } func (this *Human) Eat() { fmt.Println("Human.Eat()..." ) } func (this *Human) Walk() { fmt.Println("Human.Walk()..." ) } type SuperMan struct { Human Level int } func (this *SuperMan) Eat() { fmt.Println("SuperMan.Eat()..." ) } func (this *SuperMan) Fly() { fmt.Println("SuperMan.Fly()..." ) } func (this *SuperMan) Print() { fmt.Println("name = " , this.name) fmt.Println("sex = " , this.sex) fmt.Println("Level = " , this.Level) } func main () { h := Human{"zhangsan" , "female" } h.Eat() h.Walk() fmt.Println("=============================" ) var s SuperMan s.name = "lisi" s.sex = "female" s.Level = 10 s.Walk() s.Eat() s.Fly() s.Print() }
输出
1 2 3 4 5 6 7 8 9 Human.Eat()... Human.Walk()... ============================= Human.Walk()... SuperMan.Eat()... SuperMan.Fly()... name = lisi sex = female Level = 10
多态
基本要素:
有一个父类(有接口)
有子类(实现了父类的全部接口方法)
父类类型的变量(指针)指向(引用)子类的具体数据变量
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 package mainimport "fmt" type AnimalIF interface { Sleep() GetColor() string GetType() string } type Cat struct { color string } func (this *Cat) Sleep() { fmt.Println("Cat Sleep..." ) } func (this *Cat) GetColor() string { return this.color } func (this *Cat) GetType() string { return "Cat" } type Dog struct { color string } func (this *Dog) Sleep() { fmt.Println("Dog Sleep..." ) } func (this *Dog) GetColor() string { return this.color } func (this *Dog) GetType() string { return "Dog" } func showAnimal (animal AnimalIF) { animal.Sleep() fmt.Println("color = " , animal.GetColor()) fmt.Println("type = " , animal.GetType()) } func main () { var animal AnimalIF animal = &Cat{"White" } animal.Sleep() animal = &Dog{"Yellow" } animal.Sleep() fmt.Println("===============================" ) cat := Cat{"White" } dog := Dog{"Yellow" } showAnimal(&cat) showAnimal(&dog) }
输出
1 2 3 4 5 6 7 8 9 Cat Sleep... Dog Sleep... =============================== Cat Sleep... color = White type = CatDog Sleep... color = Yello type = Dog
interface Golang的语言中提供了断言的功能。golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}的接口,这种做法和java中的Object类型比较类似。那么在一个数据通过func funcName(interface{})的方式传进来的时候,也就意味着这个参数被自动的转为interface{}的类型。
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" func myfunc (arg interface {}) { fmt.Println("myfunc is called..." ) fmt.Println(arg) value, ok := arg.(string ) if !ok { fmt.Println("arg is not string type" ) } else { fmt.Println("arg is string type, value = " , value) fmt.Printf("value type is %T\n" , value) } } type Book struct { auth string } func main () { book := Book{"Golang" } myfunc(book) myfunc(100 ) myfunc("abc" ) myfunc(3.14 ) }
反射reflect 在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。
多插一句,Golang的gRPC也是通过反射实现的。
interface和反射
在讲反射之前,先来看看Golang关于类型设计的一些原则
变量包括(type, value)两部分
type 包括 static type
和concrete type
. 简单来说 static type是你在编码是看见的类型(如int、string),concrete type
是runtime
系统看见的类型
类型断言能否成功,取决于变量的concrete type
,而不是static type
. 因此,一个 reader
变量如果它的concrete type
也实现了write方法的话,它也可以被类型断言为writer.
接下来要讲的反射 ,就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。
在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:
value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { var a string a = "zhangsan" var allType interface {} allType = a str, _ := allType.(string ) fmt.Println(str) }
输出
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 package mainimport ( "fmt" "io" "os" ) func main () { tty, err := os.OpenFile("/dev/tty" , os.O_RDWR, 0 ) if err != nil { fmt.Println("open file error" , err) return } var r io.Reader r = tty var w io.Writer w = r.(io.Writer) w.Write([]byte ("HELLO THIS IS A TEST!!!\n" )) }
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 package mainimport "fmt" type Reader interface { ReadBook() } type Writer interface { WriteBook() } type Book struct {} func (this *Book) ReadBook() { fmt.Println("Read a Book" ) } func (this *Book) WriteBook() { fmt.Println("Write a Book" ) } func main () { b := &Book{} var r Reader r = b r.ReadBook() var w Writer w = r.(Writer) w.WriteBook() }
输出
1 2 Read a Book Write a Book
反射的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" "reflect" ) func reflectNum (arg interface {}) { fmt.Println("type: " , reflect.TypeOf(arg)) fmt.Println("value: " , reflect.ValueOf(arg)) } func main () { var num float64 = 1.2456 reflectNum(num) }
输出
1 2 type : float64value: 1.2456
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" "reflect" ) type User struct { Id int Name string Age int } func (this User) Call() { fmt.Println("user is called..." ) fmt.Printf("%v\n" , this) } func main () { user := User{1 , "lpc" , 18 } DoFiledAndMethod(user) } func DoFiledAndMethod (input interface {}) { inputType := reflect.TypeOf(input) fmt.Println("inputType is :" , inputType.Name()) inputValue := reflect.ValueOf(input) fmt.Println("inputValue is :" , 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++ { m := inputType.Method(i) fmt.Printf("%s: %v\n" , m.Name, m.Type) } }
输出
1 2 3 4 5 6 inputType is : User inputValue is : {1 lpc 18} Id: int = 1 Name: string = lpc Age: int = 18 Call: func(main.User)
结构体标签 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 package mainimport ( "fmt" "reflect" ) type resume struct { Name string `info:"name" doc:"我的名字"` Sex string `info:"sex"` } func findTag (stru interface {}) { t := reflect.TypeOf(stru).Elem() for i := 0 ; i < t.NumField(); i++ { tagInfo := t.Field(i).Tag.Get("info" ) tagDoc := t.Field(i).Tag.Get("doc" ) fmt.Println("info: " , tagInfo, "doc: " , tagDoc) } } func main () { var stru resume findTag(&stru) }
输出
1 2 info: name doc: 我的名字 info: sex doc:
应用
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 package mainimport ( "encoding/json" "fmt" ) type Movie struct { Title string `json: "title"` Year int `json: "year"` Price int `json:"rmb"` Actors []string `json:"actors"` } func main () { movie := Movie{"喜剧之王" , 2000 , 10 , []string {"xingye" , "zhangguozhi" }} jsonStr, err := json.Marshal(movie) if err != nil { fmt.Println("json marshal error" , err) return } fmt.Println("结构体--->json" ) fmt.Printf("jsonStr = %s\n" , jsonStr) myMovie := Movie{} err = json.Unmarshal(jsonStr, &myMovie) if err != nil { fmt.Println("json unmarshal error" , err) return } fmt.Println("json--->结构体" ) fmt.Printf("%v\n" , myMovie) }
输出
1 2 3 4 结构体--->json jsonStr = {"Title" :"喜剧之王" ,"Year" :2000,"rmb" :10,"actors" :["xingye" ,"zhangguozhi" ]} json--->结构体 {喜剧之王 2000 10 [xingye zhangguozhi]}
Golang高阶 Goroutine 协程并发
协程:co-routine。也叫轻量级线程。
与传统的系统级线程和进程相比,协程最大的优势在于“轻量级”。可以轻松创建上万个而不会导致系统资源衰竭。而线程和进程通常很难超过1万个。这也是协程别称“轻量级线程”的原因。
一个线程中可以有任意多个协程,但某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。
多数语言在语法层面并不直接支持协程,而是通过库的方式支持,但用库的方式支持的功能也并不完整,比如仅仅提供协程的创建、销毁与切换等能力。如果在这样的轻量级线程中调用一个同步 IO 操作,比如网络通信、本地文件读写,都会阻塞其他的并发执行轻量级线程,从而无法真正达到轻量级线程本身期望达到的目标。
在协程中,调用一个任务就像调用一个函数一样,消耗的系统资源最少!但能达到进程、线程并发相同的效果。
在一次并发任务中,进程、线程、协程均可以实现。从系统资源消耗的角度出发来看,进程相当多,线程次之,协程最少。
Go并发
Go 在语言级别支持协程,叫goroutine 。Go 语言标准库提供的所有系统调用操作(包括所有同步IO操作),都会出让CPU给其他goroutine。这让轻量级线程的切换管理不依赖于系统的线程和进程,也不需要依赖于CPU的核心数量。
有人把Go比作21世纪的C语言。第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持并发。同时,并发程序的内存管理有时候是非常复杂的,而Go语言提供了自动垃圾回收机制。
Go语言为并发编程而内置的上层API基于顺序通信进程模型CSP(communicating sequential processes)。这就意味着显式锁都是可以避免的,因为Go通过相对安全的通道发送和接受数据以实现同步,这大大地简化了并发程序的编写。
Go语言中的并发程序主要使用两种手段来实现。goroutine
和channel
。
什么是Goroutine
goroutine是Go语言并行设计的核心,有人称之为go程。 Goroutine从量级上看很像协程,它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。
一般情况下,一个普通计算机跑几十个线程就有点负载过大了,但是同样的机器却可以轻松地让成百上千个goroutine进行资源竞争。
创建Goroutine
只需在函数调⽤语句前添加 go
关键字,就可创建并发执⾏单元。开发⼈员无需了解任何执⾏细节,调度器会自动将其安排到合适的系统线程上执行。
在并发编程中,我们通常想将一个过程切分成几块,然后让每个goroutine各自负责一块工作,当一个程序启动时,主函数在一个单独的goroutine中运行,我们叫它main goroutine
。新的goroutine会用go语句来创建。而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 package mainimport ( "fmt" "time" ) func newTask () { i := 0 for { i++ fmt.Printf("new Goroutine: i = %d\n" , i) time.Sleep(1 * time.Second) } } func main () { go newTask() i := 0 for { i++ fmt.Printf("main Goroutine: i = %d\n" , i) time.Sleep(1 * time.Second) } }
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 main Goroutine: i = 1 new Goroutine: i = 1 new Goroutine: i = 2 main Goroutine: i = 2 main Goroutine: i = 3 new Goroutine: i = 3 new Goroutine: i = 4 main Goroutine: i = 4 main Goroutine: i = 5 new Goroutine: i = 5 new Goroutine: i = 6 main Goroutine: i = 6 main Goroutine: i = 7 new Goroutine: i = 7 new Goroutine: i = 8 main Goroutine: i = 8 main Goroutine: i = 9 new Goroutine: i = 9 new Goroutine: i = 10 main Goroutine: i = 10 ...
Goroutine特性
主goroutine退出后,其它的工作goroutine也会自动退出
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" "time" ) func newTask () { i := 0 for { i++ fmt.Printf("new Goroutine: i = %d\n" , i) time.Sleep(1 * time.Second) } } func main () { go newTask() fmt.Println("main goroutine exit" ) }
输出
Goexit函数
调用 runtime.Goexit()
将立即终止当前 goroutine 执⾏,调度器确保所有已注册 defer 延迟调用被执行。
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" "runtime" ) func main () { go func () { defer fmt.Println("A.defer" ) func () { defer fmt.Println("B.defer" ) runtime.Goexit() fmt.Println("B" ) }() fmt.Println("A" ) }() for { } }
输出
1 2 3 B.defer A.defer exit status 0xc000013a
Channel channel 是Go语言中的一个核心类型,可以把它看成管道。并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上又进一步降低了编程的难度。
channel是一个数据类型,主要用来解决go程的同步问题以及go程之间数据共享(数据传递)的问题。
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。goroutine 奉行通过通信来共享内存,而不是共享内存来通信。
引⽤类型 channel可用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。
定义channel变量
和map类似,channel也一个对应make创建的底层数据结构的引用 。
当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil
。
定义一个channel时,也需要定义发送到channel的值的类型。channel可以使用内置的make()函数来创建:
chan 是创建channel所需使用的关键字。Type 代表指定channel收发数据的类型。
1 2 make (chan Type) make (chan Type, capacity)
当 参数capacity= 0 时,channel 是无缓冲阻塞读写的;当capacity > 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity个元素才阻塞写入。
channel非常像生活中的管道,一边可以存放东西,另一边可以取出东西。channel通过操作符 <-
来接收和发送数据,发送和接收数据语法:
1 2 3 4 channel <- value <-channel x := <-channel x, ok := <-channel
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得goroutine同步变的更加的简单,而不需要显式的lock 。
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" ) func main () { c := make (chan int ) go func () { defer fmt.Println("goroutine结束" ) fmt.Println("goroutine正在运行……" ) c <- 666 }() num := <-c fmt.Println("num = " , num) fmt.Println("main goroutine结束" ) }
输出
1 2 3 4 goroutine正在运行…… goroutine结束 num = 666 main goroutine结束