学习视频:8 小时转职 Golang 工程师 ,这门课很适合有一定开发经验的小伙伴,强推!
参考博客链接:https://blog.csdn.net/weixin_43734095/category_11618348.html
学习路线:https://blog.csdn.net/weixin_45304503/article/details/127041166
【Golang 快速入门1】基础语法 + 面向对象
Golang 语言特性 Golang 的优势 极简单的部署 方式:可直接编译成机器码、不依赖其他库、直接运行即可部署。
静态 类型语言,编译的时候可以检查出大多数问题。
语言层面的并发 :天生的基因支持、充分的利用多核
1 2 3 4 5 6 7 8 9 10 11 12 func goFunc (i int ) { fmt.Println("goroutine " , i, " ..." ) }func main () { for i := 0 ; i < 1000 ; i++ { go goFunc(i) } time.Sleep(time.Second) }
强大的标准库 :runtime 系统调度机制、高效的 CG 垃圾回收、丰富的标准库
“大厂” 领军:Google、facebook、Tencent、Baidu、七牛、字节 …
不同语言的斐波那契数列算法 编译 + 运行 时间对比:
Golang 的应用场景 1、云计算基础设施领域 :
代表项目:docker、kubernetes、etcd、consul、cloud flare CDN、七牛云存储 等。
2、基础后端软件 :
代表项目:tidb、influxdb、 cockroach 等。
3、微服务
代表项目:go-kit、 micro、 monzo bank 的 typhon、bilibili 等。
4、互联网基础设施
代表项目:以太坊、hyperledger 等。
Golang 明星作品:Docker 、Kubernetes
Golang 的不足 1、包管理,大部分包都托管在 Github 上。
像我们熟悉的 maven、npm 等都有专门的包管理组织;
托管在 Github 上的代码容易被作者个人操作影响到使用该项目的工程。
2、无泛化类型。
据说很快就会有了。
3、所有 Exception 都用 Error 来处理(有争议)。
4、对 C 的降级处理,并非无缝,没有 C 降级到 asm 那么完美。(序列化问题)
Golang 基础语法
main主方法和注意点 1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" "time" ) func main () { fmt.Println("Hello Go!" ) time.Sleep(1 * time.Second) }
Go语言的格式化输出中%d%T%v%b等的含义 %% %字面量 %b 一个二进制整数,将一个整数格式转化为二进制的表达方式 %c 一个Unicode的字符 %d 十进制整数 %o 八进制整数 %x 小写的十六进制数值 %X 大写的十六进制数值 %U 一个Unicode表示法表示的整型码值 %s 输出以原生的UTF8字节表示的字符,如果console不支持utf8编码,则会乱码 %t 以true或者false的方式输出布尔值 %v 使用默认格式输出值,或者如果方法存在,则使用类性值的String()方法输出自定义值 %T 输出值的类型 %+v 结构体字段名称+值 %#v 结构体的值
1、变量 输出变量类型的方法:
1 2 3 var a int fmt.Printf("type of a = %T\n" , a)
局部变量 的声明:
1 2 3 4 5 6 7 8 9 10 11 var a int var b int = 100 var c = 100 d := 100
全局变量 的声明:以上只有方法四不支持(编译会报错)
多变量 的声明:
1 2 3 4 5 6 7 8 9 var xx, yy int = 100 , 200 var kk, ll = 100 , "Aceld" var ( vv int = 100 jj bool = true )
2、常量与 iota 使用 const
定义常量,常量是只读的,不允许修改。
1 2 3 4 5 6 const a int = 10 const ( a = 10 b = 20 )
const 可以用来定义枚举:
1 2 3 4 5 const ( BEIJING = 0 SHANGHAI = 1 SHENZHEN = 3 )
const 可以和 iota 一起使用来定义有规则的枚举 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const ( BEIJING = iota SHANGHAI SHENZHEN )const ( a, b = iota +1 , iota +2 c, d e, f g, h = iota * 2 , iota *3 i, k )
3、string字符串 对于字符串操作的 4 个包:bytes、strings、strconv、unicode
bytes 包操作 []byte
。因为字符串是只读的,因此逐步构创建字符串会导致很多分配和复制,使用 bytes.Buffer
类型会更高。strings 包提供 切割、索引、前缀、查找、替换 等功能。strconv 包提供 布尔型、整型数、浮点数 和对应字符串的相互转换,还提供了双引号转义相关的转换。unicode 包提供了 IsDigit、IsLetter、IsUpper、IsLower 等类似功能,用于给字符分类。如果 string 中包含汉字,要注意:
UTF-8 编码中,一个汉字需要 3 个字节 ,通过 len()
获取的是字符串占据的字节数 。 如果 string 中包含汉字,要注意:
UTF-8 编码中,一个汉字需要 3 个字节 ,通过 len()
获取的是字符串占据的字节数 。 1 2 str1 := "hello 世界" fmt.Println(len (str1))
如果想要得到字符串本身的长度 ,可以将 string 转为 rune 数组再计算: 1 2 str2 := "hello 世界" fmt.Println(len ([]rune (str2)))
字符串遍历 byte 是 uint8 的别名
rune 是 int32 的别名,相当于 Go 里面的 char
如果包含汉字,以下遍历方式会出现乱码:
1 2 3 4 5 6 str := "你好世界!" for i := 0 ; i < len (str); i++ { fmt.Printf("%c" , str[i]) }
1 2 3 4 5 6 str := "你好世界!" newStr := []rune (str)for i := 0 ; i < len (newStr); i++ { fmt.Printf("%c" , newStr[i]) }
range 按照字符遍历,前面的 for 按照字节遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 str := "你好世界123" for index, value := range str { fmt.Printf("index = %d value = %c\n" , index, value) }
strings 包 字符串比较 :使用 strings.Compare
比较两个字符串的字典序
1 2 3 strings.Compare("aaa" , "bbb" ) strings.Compare("baa" , "abb" ) strings.Compare("aaa" , "aaa" )
查找函数 :使用 strings.Index
查找字符串中子串的位置(第 1 个),不存在返回 -1
1 strings.Index("hello world" , "o" )
类似的,使用 strings.LastIndex
查找字符串子串出现的最后一个位置,不存在返回 -1
1 strings.Index("hello world" , "o" )
Count、Repeat :
使用 strings.Count
统计子串在整体中出现的次数:
1 strings.Count("abc abc abab abc" , "abc" )
使用 strings.Repeat
将字符串重复指定次数:
1 strings.Repeat("abc" , 3 )
Replace、Split、Join :
strings.Replace
实现字符串替换
1 2 3 4 5 6 7 8 str := "acaacccc" strings.Replace(str, "a" , "b" , 2 ) strings.Replace(str, "a" , "b" , -1 ) strings.ReplaceAll(str, "a" , "b" )
strings.Split
实现字符串切割
1 2 3 4 str := "abc,bbc,bbd" slice := strings.Split(str, "," ) fmt.Println(slice)
strings.Join
实现字符串拼接
1 2 3 4 slice := []string {"aab" , "aba" , "baa" } str := strings.Join(slice, "," ) fmt.Println(str
bytes 包 Buffer 是 bytes 包中定义的 type Buffer struct {...}
,Buffer 是一个变长的可读可写的缓冲区。
创建缓冲器 :bytes.NewBufferString
、bytes.NewBuffer
1 2 3 4 5 6 7 8 9 10 11 12 func main() { buf1 := bytes.NewBufferString("hello") buf2 := bytes.NewBuffer([]byte("hello")) buf3 := bytes.NewBuffer([]byte{'h', 'e', 'l', 'l', 'o'}) fmt.Printf("%v,%v,%v\n", buf1, buf2, buf3) fmt.Printf("%v,%v,%v\n", buf1.Bytes(), buf2.Bytes(), buf3.Bytes()) buf4 := bytes.NewBufferString("") buf5 := bytes.NewBuffer([]byte{}) fmt.Println(buf4.Bytes(), buf5.Bytes()) }
1 2 3 hello,hello,hello [104 101 108 108 111],[104 101 108 108 111],[104 101 108 108 111] [] []
写入缓冲器 :Write
、WriteString
、WriteByte
、WriteRune
、WriteTo
1 2 3 4 5 6 7 8 9 10 11 12 func main () { buf := bytes.NewBufferString("a" ) fmt.Printf("%v, %v\n" , buf.String(), buf.Bytes()) buf.Write([]byte ("b" )) buf.WriteString("c" ) buf.WriteByte('d' ) buf.WriteRune('e' ) fmt.Printf("%v, %v\n" , buf.String(), buf.Bytes()) }
缓冲区原理介绍 :Go 字节缓冲区底层以字节切片做存储,切片存在长度 len 与容量 cap
缓冲区从长度 len 的位置开始写,当 len > cap 时,会自动扩容 缓冲区从内置标记 off 位置开始读(off 始终记录读的起始位置) 当 off == len 时,表明缓冲区已读完,读完就重置缓冲区 len = off = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { byteSlice := make ([]byte , 20 ) byteSlice[0 ] = 1 byteBuffer := bytes.NewBuffer(byteSlice) c, _ := byteBuffer.ReadByte() fmt.Printf("len:%d, c=%d\n" , byteBuffer.Len(), c) byteBuffer.Reset() fmt.Printf("len:%d\n" , byteBuffer.Len()) byteBuffer.Write([]byte ("hello byte buffer" )) fmt.Printf("len:%d\n" , byteBuffer.Len()) byteBuffer.Next(4 ) c, _ = byteBuffer.ReadByte() fmt.Printf("第5个字节:%d\n" , c) byteBuffer.Truncate(3 ) fmt.Printf("len:%d\n" , byteBuffer.Len()) byteBuffer.WriteByte(96 ) byteBuffer.Next(3 ) c, _ = byteBuffer.ReadByte() fmt.Printf("第9个字节:%d\n" , c) }
缓冲区:
1 2 3 4 5 6 7 8 9 10 func main () { buf := &bytes.Buffer{} buf.WriteString("abc?def" ) str, _ := buf.ReadString('?' ) fmt.Println("str = " , str) fmt.Println("buff = " , buf.String()) }
缓冲区读数据 :Read
、ReadByte
、ReadByes
、ReadString
、ReadRune
、ReadFrom
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 func main () { log.SetFlags(log.Lshortfile) buff := bytes.NewBufferString("123456789" ) log.Println("buff = " , buff.String()) s := make ([]byte , 4 ) n, _ := buff.Read(s) log.Println("buff = " , buff.String()) log.Println("s = " , string (s)) log.Println("n = " , n) n, _ = buff.Read(s) log.Println("buff = " , buff.String()) log.Println("s = " , string (s)) log.Println("n = " , n) n, _ = buff.Read(s) log.Println("buff = " , buff.String()) log.Println("s = " , string (s)) log.Println("n = " , n) buff.Reset() buff.WriteString("abcdefg" ) log.Println("buff = " , buff.String()) b, _ := buff.ReadByte() log.Println("b = " , string (b)) log.Println("buff = " , buff.String()) b, _ = buff.ReadByte() log.Println("b = " , string (b)) log.Println("buff = " , buff.String()) bs, _ := buff.ReadBytes('e' ) log.Println("bs = " , string (bs)) log.Println("buff = " , buff.String()) buff.Reset() buff.WriteString("编译输出GO" ) r, l, _ := buff.ReadRune() log.Println("r = " , r, ", l = " , l, ", string(r) = " , string (r)) buff.Reset() buff.WriteString("qwer" ) str, _ := buff.ReadString('?' ) log.Println("str = " , str) log.Println("buff = " , buff.String()) buff.WriteString("qwer" ) str, _ = buff.ReadString('w' ) log.Println("str = " , str) log.Println("buff = " , buff.String()) file, _ := os.Open("doc.go" ) buff.Reset() buff.ReadFrom(file) log.Println("doc.go = " , buff.String()) buff.Reset() buff.WriteString("中国人" ) cbyte := buff.Bytes() log.Println("cbyte = " , cbyte) }
strconv 包 字符串转 []byte :
字符串 —> 整数 :使用 strconv.Atoi
或 strconv.ParseInt
1 2 3 4 5 6 7 8 9 i, _ := strconv.Atoi("33234" ) fmt.Printf("%T\n" , i) i2, _ := strconv.ParseInt("33234" , 10 , 0 ) fmt.Printf("%T\n" , i2)
字符串 —> 浮点数 :使用 strconv.ParseFloat
1 2 3 4 5 6 val, _ := strconv.ParseFloat("33.33" , 32 ) fmt.Printf("type: %T\n" , val) val2, _ := strconv.ParseFloat("33.33" , 64 ) fmt.Printf("type: %T\n" , val2)
整数 —> 字符串 :使用 strconv.Iota
或 strconv.FormatInt
1 2 3 4 5 6 7 8 num := 180 f1 := strconv.Itoa(num) f2 := strconv.FormatInt(int64 (num), 10 )
浮点数 —> 整数 :使用 strconv.FormatFloat
1 2 3 4 5 6 7 num := 23423134.323422 fmt.Println(strconv.FormatFloat(float64 (num), 'f' , -1 , 64 )) fmt.Println(strconv.FormatFloat(float64 (num), 'b' , -1 , 64 )) fmt.Println(strconv.FormatFloat(float64 (num), 'e' , -1 , 64 )) fmt.Println(strconv.FormatFloat(float64 (num), 'E' , -1 , 64 )) fmt.Println(strconv.FormatFloat(float64 (num), 'g' , -1 , 64 )) fmt.Println(strconv.FormatFloat(float64 (num), 'G' , -1 , 64 ))
1 2 3 4 5 6 23423134.323422 6287599743057036p-28 2.3423134323422e+07 2.3423134323422E+07 2.3423134323422e+07 2.3423134323422E+07
字符串 和 bool 类型转换 :
1 2 3 4 5 6 7 flagBool, _ := strconv.ParseBool("true" ) flagStr := strconv.FormatBool(true )
unicode 包 /src/unicode/letter.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 func IsUpper (r rune ) bool func IsLower (r rune ) bool func IsTitle (r rune ) bool func ToUpper (r rune ) rune func ToLower (r rune ) rune func ToTitle (r rune ) rune func To (_case int , r rune ) rune
/src/unicode/digit.go
:
1 2 func IsDigit (r rune ) bool
/src/unicode/graphic.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 func IsNumber (r rune ) bool func IsLetter (r rune ) bool func IsSpace (r rune ) bool func IsControl (r rune ) bool func IsGraphic (r rune ) bool func IsPrint (r rune ) bool func IsPunct (r rune ) bool func IsSymbol (r rune ) bool func IsMark (r rune ) bool func IsOneOf (set []*RangeTable, r rune ) bool
4、循环语句 go 语言中的 for 循环有 3 种形式:
1 2 3 for init; condition; post { }for condition { }for { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func main () { numbers := [6 ]int {1 , 2 , 3 , 5 } for i := 0 ; i < len (numbers); i++ { fmt.Println(numbers[i]) } i := 0 for i < len (numbers) { fmt.Println(numbers[i]) i++ } for i, x := range numbers { fmt.Printf("index: %d, value: %d\n" , i, x) } for { fmt.Println("endless..." ) } }
range 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func main () { numbers := []int {1 , 2 , 3 , 4 , 5 , 6 } for i := range numbers { fmt.Println(numbers[i]) } for _, n := range numbers { fmt.Println(n) } for range numbers { } m := map [string ]int {"a" : 1 , "b" : 2 } for k, v := range m { fmt.Println(k, v) } }
注意:range 会复制对象
1 2 3 4 5 6 7 8 9 10 11 func main () { a := [3 ]int {0 , 1 , 2 } for i, v := range a { if i == 0 { a[1 ], a[2 ] = 999 , 999 fmt.Println(a) } a[i] = v + 100 } fmt.Println(a) }
5、函数 多返回值 单返回值的函数:
1 2 3 func foo1 (a string , b int ) int { return 100 }
多返回值的函数:
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 func foo2 (a string , b int ) (int , int ) { return 666 , 777 }func foo3 (a string , b int ) (r1 int , r2 int ) { fmt.Println("r1 = " , r1) fmt.Println("r2 = " , r2) r1 = 1000 r2 = 2000 return }func foo4 (a string , b int ) (r1, r2 int ) { r1 = 1000 r2 = 2000 return }
init 函数 每个 go 程序都会在一开始执行 init() 函数,可以用来做一些初始化操作:
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func init () { fmt.Println("init..." ) }func main () { fmt.Println("hello world!" ) }
如果一个程序依赖了多个包,它的执行流程如下图:
制作包的时候,项目路径如下:
1 2 3 4 5 6 $GOPATH/GolangStudy/5-init/ ├── lib1/ │ └── lib1.go ├── lib2/ │ └── lib2.go └── main.go
1 2 3 4 lib1 .init() ... lib2 .init() ... lib1Test() lib2Test()
闭包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func a () func () int { i := 0 b := func () int { i++ fmt.Println(i) return i } return b }func main () { c := a() c() c() c() a() }
6、指针(*取值 &取地址)
经典:在函数中交换两数的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func swap (pa *int , pb *int ) { var temp int temp = *pa *pa = *pb *pb = temp }func main () { var a, b int = 10 , 20 swap(&a, &b) fmt.Println("a = " , a, " b = " , b) }
7、defer defer 声明的语句会在当前函数执行完之后调用:
1 2 3 4 func main () { defer fmt.Println("main end" ) fmt.Println("main::hello go " ) }
1 2 main::hello go main end
如果有多个 defer,依次入栈,函数返回后依次出栈执行:
上图执行顺序:func3() -> func2() -> func1()
关于 defer 和 return 谁先谁后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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() }
结论:return 之后的语句先执⾏,defer 后的语句后执⾏ 。
8、数组和切片 slice Golang 默认都是采用值传递,有些值天生就是指针:slice、map、channel。
注意:定长数组是值传递,slice 是指针传递 。
数组(固定长度数组和动态数组) go语言之切片即动态数组
切片和数组的类型有什么不一样,我们可以打印一下,就可以知道两者的区别了,数组是容量的,所以中括号中有容量,切片的动态数组,是没有容量,这是数组和切片最大的区别
切片操作是引用,修改切片后的数组也会导致原来的切片数据变化,这里和python是不同的 1 2 3 4 test8_4 := [20 ] int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 } test8_5 := [] int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 } fmt.Println(reflect.TypeOf(test8_4),reflect.TypeOf(test8_5))
声明数组的方式:(固定长度的数组)
1 2 3 var array1 [10 ]int array2 := [10 ]int {1 ,2 ,3 ,4 } array3 := [4 ]int {1 ,2 ,3 ,4 }
数组的长度是固定的,并且在传参的时候,严格匹配数组类型
1 2 3 4 5 6 7 8 9 10 11 func printArray (myArray [4]int ) { fmt.Println(myArray) myArray[0 ] = 666 }func main () { myArray := [4 ]int {1 , 2 , 3 , 4 } printArray(myArray) fmt.Println(myArray) }
myArray := [...]int{1, 2, 3, 4}
是自动计算数组长度,但并不是引用传递。
声明动态数组和声明数组一样,只是不用写长度。
1 2 3 4 5 6 7 8 9 10 11 func printArray (myArray []int ) { fmt.Println(myArray) myArray[0 ] = 10 }func main () { myArray := []int {1 , 2 , 3 , 4 } printArray(myArray) fmt.Println(myArray) }
切片 slice slice 的声明方式:通过 make 关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 slice1 := []int {1 , 2 , 3 } var slice2 []int slice2 = make ([]int , 3 ) var slice3 []int = make ([]int , 3 ) slice4 := make ([]int , 3 )
len() 和 cap() 函数:
len:长度,表示左指针⾄右指针之间的距离。 cap:容量,表示指针至底层数组末尾的距离。
切⽚的扩容机制,append 的时候,如果长度增加后超过容量,则将容量增加 2 倍。
1.18 版本,当 cap
小于 256
,则按照 cap *2
扩容;当cap
大于等于 256
,则按照 cap + (cap+3*256)/4
扩容(目的,将扩容速率从 2
逐渐降低到 1.25
))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var numbers = make ([]int , 3 , 5 ) fmt.Printf("len = %d, cap = %d, slice = %v\n" , len (numbers), cap (numbers), numbers) numbers = append (numbers, 1 ) fmt.Printf("len = %d, cap = %d, slice = %v\n" , len (numbers), cap (numbers), numbers) numbers = append (numbers, 2 ) fmt.Printf("len = %d, cap = %d, slice = %v\n" , len (numbers), cap (numbers), numbers) numbers = append (numbers, 3 ) fmt.Printf("len = %d, cap = %d, slice = %v\n" , len (numbers), cap (numbers), numbers)
1 2 3 4 len = 3, cap = 5, slice = [0 0 0] len = 4, cap = 5, slice = [0 0 0 1] len = 5, cap = 5, slice = [0 0 0 1 2] len = 6, cap = 10, slice = [0 0 0 1 2 3]
slice 操作 slice 截取是浅拷贝,若想深拷贝需要使用 copy
可以通过设置下限以及上限设置截取切片 [lower-bound: upper-bound]
,实例:
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 func main () { numbers := []int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 } fmt.Println(numbers) fmt.Println("number ==" , 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 ) fmt.Println(numbers1) numbers2 := numbers[:2 ] fmt.Println(numbers2) numbers3 := numbers[2 :5 ] fmt.Println(numbers3) }
1 2 3 4 5 6 7 8 [0 1 2 3 4 5 6 7 8] number == [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] [] [0 1] [2 3 4]
利用 copy 函数拷贝切片,是深拷贝 。
1 2 3 4 5 slice1 := []int {1 , 2 , 3 } slice2 := make ([]int , 3 )copy (slice2, slice1) slice2[0 ] = 10 fmt.Println(slice1)
直接赋值切片,是浅拷贝 。
1 2 3 4 slice1 := []int {1 , 2 , 3 } slice2 := slice1 slice2[0 ] = 10 fmt.Println(slice1)
… ...
是 Go 的一种语法糖。
用法 1:函数可以用来接受多个不确定数量的参数。 用法 2:slice 可以被打散进行传递。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func test (args ...string ) { for _, v := range args { fmt.Println(v) } }func main () { var ss = []string { "abc" , "efg" , "hij" , "123" , } test(ss...) }
9、map slice、map、channel 都是引用类型,声明后还需要初始化分配内存,即 make
map 的声明 map 的第一种声明方式:
1 2 3 4 5 6 7 8 9 10 11 12 var myMap1 map [string ]string fmt.Println(myMap1 == nil ) myMap1 = make (map [string ]string , 10 ) myMap1["one" ] = "java" myMap1["two" ] = "c++" myMap1["three" ] = "python" fmt.Println(myMap1)
map 的第二种声明方式:
1 2 3 4 5 6 7 myMap2 := make (map [int ]string ) myMap2[1 ] = "java" myMap2[2 ] = "c++" myMap2[3 ] = "python" fmt.Println(myMap2)
map 的第三种声明方式:
1 2 3 4 5 6 7 8 myMap3 := map [string ]string { "one" : "php" , "two" : "c++" , "three" : "python" , } fmt.Println(myMap3)
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 func printMap (cityMap map [string ]string ) { for key, value := range cityMap { fmt.Println("key = " , key+", value = " , value) } }func AddValue (cityMap map [string ]string ) { cityMap["England" ] = "London" }func main () { cityMap := make (map [string ]string ) cityMap["China" ] = "Beijing" cityMap["Japan" ] = "Tokyo" cityMap["USA" ] = "NewYork" delete (cityMap, "China" ) printMap(cityMap) fmt.Println("-------" ) cityMap["USA" ] = "DC" AddValue(cityMap) printMap(cityMap) }
1 2 3 4 5 6 key = Japan, value = Tokyo key = USA, value = NewYork ------- key = England, value = London key = Japan, value = Tokyo key = USA, value = DC
判断 map 中 key 值是否存在:直接取值,返回有两个返回值,通过第 2 个返回值判断。
1 2 3 4 5 6 7 m := make (map [string ]interface {}) m["a" ] = "AAA" if _, ok := m["ba" ]; ok { fmt.Println("存在" ) } else { fmt.Println("不存在" ) }
10、error(panic和recover配合捕获异常) 捕获系统抛出异常:
1 2 3 4 5 6 7 8 9 10 11 func main () { defer func () { if err := recover (); err != nil { fmt.Println("捕获:" , err) } }() nums := []int {1 , 2 , 3 } fmt.Println(nums[4 ]) }
手动抛出异常并捕获:
1 2 3 4 5 6 7 8 9 func main () { defer func () { if err := recover (); err != nil { fmt.Println("捕获:" , err) } }() panic ("出现异常!" ) }
返回异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func getCircleArea (radius float32 ) (area float32 , err error ) { if radius < 0 { err = errors.New("半径不能为负" ) return } area = 3.14 * radius * radius return }func main () { area, err := getCircleArea(-5 ) if err != nil { fmt.Println(err) } else { fmt.Println(area) } }
自定义异常:
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 type PathError struct { path string op string createTime string message string }func (p *PathError) Error() string { return fmt.Sprintf("path=%s \nop=%s \ncreateTime=%s \nmessage=%s" , p.path, p.op, p.createTime, p.message) }func Open (filename string ) error { file, err := os.Open(filename) if err != nil { return &PathError{ path: filename, op: "read" , message: err.Error(), createTime: fmt.Sprintf("%v" , time.Now()), } } defer file.Close() return nil }func main () { err := Open("test.txt" ) switch v := err.(type ) { case *PathError: fmt.Println("get path error," , v) default : } }
面向对象编程 1、type 利用 type 可以声明某个类型的别名(理解为声明一种新的数据类型)
1 2 3 4 5 6 7 type myint int func main () { var a myint = 10 fmt.Println("a = " , a) fmt.Printf("type of a = %T\n" , a) }
1 2 a = 10 type of a = main.myint
2、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 type Book struct { title string price string }func changeBook (book Book) { book.price = "666" }func changeBook2 (book *Book) { book.price = "777" }func main () { var book Book book.title = "Golang" book.price = "111" fmt.Printf("%v\n" , book) changeBook(book) fmt.Printf("%v\n" , book) changeBook2(&book) fmt.Printf("%v\n" , book) }
一道 struct 与指针面试题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type student struct { name string age int }func main () { m := make (map [string ]*student) stus := []student{ {name: "aaa" , age: 18 }, {name: "bbb" , age: 23 }, {name: "ccc" , age: 28 }, } for _, stu := range stus { m[stu.name] = &stu } for k, v := range m { fmt.Println(k, "=>" , v.name) } }
解决方法 1:
1 2 3 4 5 for _, stu := range stus { temp := stu m[stu.name] = &temp }
解决方法 2:
1 2 3 4 for i, stu := range stus { m[stu.name] = &stus[i] }
3、方法 方法 :包含了接受者的函数,接受者可以是命名类型或结构体类型的值或者指针。
方法和普通函数的区别 :
对于普通函数,参数为值类型时,不能将指针类型的数据直接传递,反之亦然。 对于方法,接收者为值类型时,可以直接用指针类型的变量调用方法(反过来也可以)。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func valueIntTest (a int ) int { return a + 10 }func pointerIntTest (a *int ) int { return *a + 10 }func structTestValue () { a := 2 fmt.Println("valueIntTest:" , valueIntTest(a)) b := 5 fmt.Println("pointerIntTest:" , pointerIntTest(&b)) }
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 type PersonD struct { id int name string }func (p PersonD) valueShowName() { fmt.Println(p.name) }func (p *PersonD) pointShowName() { fmt.Println(p.name) }func structTestFunc () { personValue := PersonD{101 , "hello world" } personValue.valueShowName() personValue.pointShowName() personPointer := &PersonD{102 , "hello golang" } personPointer.valueShowName() personPointer.pointShowName() }
4、封装 Golang 中,类名、属性名、⽅法名 首字⺟大写 表示对外(其他包)可以访问,否则只能够在本包内访问。
1 2 3 4 5 6 7 type Hero struct { Name string Ad int level int }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func (h *Hero) Show() { fmt.Println("Name = " , h.Name) fmt.Println("Ad = " , h.Ad) fmt.Println("Level = " , h.level) fmt.Println("---------" ) }func (h *Hero) GetName() string { return h.Name }func (h *Hero) SetName(newName string ) { h.Name = newName }func main () { hero := Hero{Name: "zhang3" , Ad: 100 } hero.Show() hero.SetName("li4" ) hero.Show() }
1 2 3 4 5 6 7 8 Name = zhang3 Ad = 100 Level = 0 --------- Name = li4 Ad = 100 Level = 0 ---------
5、继承(go中用组合代替继承) Golang 通过匿名字段 实现继承的效果:
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 type Human struct { name string sex string }func (h *Human) Eat() { fmt.Println("Human.Eat()..." ) }func (h *Human) Walk() { fmt.Println("Human.Walk()..." ) }type SuperMan struct { Human level int }func (s *SuperMan) Eat() { fmt.Println("SuperMan.Eat()..." ) }func (s *SuperMan) Fly() { fmt.Println("SuperMan.Fly()..." ) }func main () { var s SuperMan s.name = "li4" s.sex = "male" s.level = 88 s.Walk() s.Eat() s.Fly() }
1 2 3 Human.Walk()... SuperMan.Eat()... SuperMan.Fly()...
6、多态 Go 中接口相关文章:理解Duck Typing (鸭子模型)
Go 接口是一组方法的集合,可以理解为抽象的类型。它提供了一种非侵入式的接口。任何类型,只要实现了该接口中方法集,那么就属于这个类型。 (如果没有完全实现接口的方法,接口的指针就不会指向类)
Golang 中多态的基本要素:
1 2 3 4 5 6 type AnimalIF interface { Sleep() GetColor() string GetType() string }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Cat struct { color string }func (c *Cat) Sleep() { fmt.Println("Cat is Sleep" ) }func (c *Cat) GetColor() string { return c.color }func (c *Cat) GetType() string { return "Cat" }
父类类型的变量(指针)指向(引用)子类的具体数据变量 1 2 3 4 var animal AnimalIF animal = &Cat{"Green" } animal.Sleep()
不同接收者实现接口 1 2 3 4 5 6 7 type Mover interface { move() }type dog struct { name string }
值类型接收者实现接口 :可以同时接收 值类型 和 指针类型。
Go 语言中有对指针类型变量求值的语法糖,dog 指针 dog2
内部会自动求值 *dog2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func (d dog) move() { fmt.Println(d.name, "is moving" ) }func main () { var m Mover var dog1 = dog{"dog1" } m = dog1 m.move() var dog2 = &dog{"dog2" } m = dog2 m.move() }
指针类型接收者实现接口 :只能接收指针类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func (d *dog) move() { fmt.Println(d.name, "is moving" ) }func main () { var m Mover var dog2 = &dog{"dog2" } m = dog2 m.move() }
一道面试题:以下代码能否通过编译?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type People interface { Speak(string ) string }type Student struct {}func (stu *Student) Speak(think string ) (talk string ) { if think == "sb" { talk = "你是个大帅比" } else { talk = "您好" } return }func main () { var peo People = Student{} think := "bitch" fmt.Println(peo.Speak(think)) }
不能。修改 var peo People = Student{}
为 var peo People = &Student{}
即可。
7、通用万能类型和断言 interface{}
表示空接口,可以用它引用任意类型的数据类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func myFunc (arg interface {}) { fmt.Println(arg) }type Book struct { auth string }func main () { book := Book{"Golang" } myFunc(book) myFunc(100 ) myFunc("abc" ) myFunc(3.14 ) }
Golang 给 interface{}
提供类型断言机制,用来区分此时引用的类型:
注意断言这个操作会有两个返回值
1 2 3 4 5 6 7 8 9 10 func myFunc (arg interface {}) { 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) } }
一个接口的值(简称接口值)是由一个 具体类型 和 具体类型的值 两部分组成的。
这两部分分别称为接口的动态类型和动态值。
1 2 3 4 var w io.Writer w = os.Stdout w = new (bytes.Buffer) w = nil
switch 判断多个断言:
1 2 3 4 5 6 7 8 9 10 11 12 func justifyType (x interface {}) { switch v := x.(type ) { case string : fmt.Printf("x is a string,value is %v\n" , v) case int : fmt.Printf("x is a int is %v\n" , v) case bool : fmt.Printf("x is a bool is %v\n" , v) default : fmt.Println("unsupport type!" ) } }