封装主要是通过访问权限控制实现的。
在Java中,共有public 、protected、default、private这四种权限控制。
而相应的在golang中,是通过约定来实现权限控制的。变量名首字母大写,相当于java中的public,首字母小写,相当于private。同一个包中访问,相当于default。由于go没有继承,也就没有protected。
虽然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
|
package main
import "fmt"
type Person struct{
Name string
}
func (p *Person) Say(){
fmt.Println("Person name is :",p.Name)
}
type Boy struct{
HaveFuJi bool
Person
}
func (b *Boy) Say(){
fmt.Println("Boy name is :",b.Name)
}
func main(){
boy := &Boy{
HaveFuJi:true,
}
boy.Name="hkh"
boy.Say()
}
//输出结果
Boy name is : hkh
|
Java 中的多态是通过 extends class 或者 implements interface 实现的,在 golang 中既没有 extends,也没有 implements ,那么 go 中多态是如何实现的呢 ?
答案:在golang中,只要某个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
|
package main
import "fmt"
type Person interface {
Action()
}
type Girl struct {
Name string
}
func (g *Girl) Action() {
fmt.Printf("My name is %v\n", g.Name)
}
type Boy struct {
Name string
}
func (b *Boy) Action() {
fmt.Printf("My name is %v\n", b.Name)
}
func main() {
girl := &Girl{Name: "Beautiful"}
boy := &Boy{Name: "Handsome"}
girl.Action()
boy.Action()
}
//输出结果
My name is Beautiful
My name is Handsome
|
Go 语言中的反射与其他语言有比较大的不同,Golang 中的发射主要涉及到两个基本概念 Type 和 Value,它们也是 Go 语言包中 reflect 包里最重要的两个类型。
TypeOf、ValueOf#
使用反射获取变量基本类型的语法:
1
|
reflect.TypeOf(varname)
|
使用反射获取变量值的基本语法:
1
|
reflect.ValueOf(varname)
|
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 main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func main() {
var x = 3.4
var str = "HaiCoder"
person := &Person{
Name: "HaiCoder",
Age: 18,
}
fmt.Println("x type =", reflect.TypeOf(x))
fmt.Println("str type =", reflect.TypeOf(str))
fmt.Println("person type =", reflect.TypeOf(person))
fmt.Println("x value =", reflect.ValueOf(x))
fmt.Println("str value =", reflect.ValueOf(str))
fmt.Println("person value =", reflect.ValueOf(person))
}
|
1
2
3
4
5
6
|
x type = float64
str type = string
person type = *main.Person
x value = 3.4
str value = HaiCoder
person value = &{HaiCoder 18}
|
Kind#
反射获取变量类型的详细信息语法:
1
|
reflect.TypeOf(varname).Kind()
|
类型详细信息和基本数据类型比较的语法:
1
|
v.Kind() == reflect.Float64
|
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 main
import (
"fmt"
"reflect"
)
func main() {
var x = 1024
var str = "HaiCoder"
typeX := reflect.TypeOf(x)
typeStr := reflect.TypeOf(str)
typexKind := typeX.Kind()
typeStrKind := typeStr.Kind()
fmt.Println("x type =", typeX, ", Kind =", typexKind)
fmt.Println("str type =", typeStr, ", Kind =", typeStrKind)
if typexKind == reflect.Int {
fmt.Println("typexKind is int")
} else {
fmt.Println("typexKind is not int")
}
if typeStrKind == reflect.String {
fmt.Println("typeStrKind is string")
} else {
fmt.Println("typeStrKind is not string")
}
}
|
1
2
3
4
|
x type = int , Kind = int
str type = string , Kind = string
typexKind is int
typeStrKind is string
|
Elem#
使用 reflect.ValueOf
的 Elem() 方法传入我们要获取的变量,可以获取该变量的指针所指向的对象的信息。如果 ValueOf 传入的是值类型,那么使用 Elem 获取地址会 panic。
因此,使用 Elem() 获取地址的前提是,我们传入的 varname 是一个地址,而不是一个值类型。
1
|
reflect.ValueOf(varname).Elem()
|
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 main
import (
"fmt"
"reflect"
)
func main() {
var x = 1024
var str = "HaiCoder"
pValueX := reflect.ValueOf(&x)
pValueStr := reflect.ValueOf(&str)
fmt.Println("pvalueX =", pValueX)
fmt.Println("pvalueStr =", pValueStr)
pValueElemX := pValueX.Elem()
pValueElemStr := pValueStr.Elem()
fmt.Println("pValueElemX =", pValueElemX)
fmt.Println("pValueElemStr =", pValueElemStr)
valueX := reflect.ValueOf(x)
valueStr := reflect.ValueOf(str)
fmt.Println("valueX =", valueX)
fmt.Println("valueStr =", valueStr)
valueElemX := valueX.Elem()
valueElemStr := valueStr.Elem()
fmt.Println("valueElemX =", valueElemX)
fmt.Println("valueElemStr =", valueElemStr)
}
|
1
2
3
4
5
6
7
|
pvalueX = 0xc000018088
pvalueStr = 0xc000040250
pValueElemX = 1024
pValueElemStr = HaiCoder
valueX = 1024
valueStr = HaiCoder
panic: reflect: call of reflect.Value.Elem on int Value
|
CanSet()#
CanSet要和Elem配合使用,并且reflect.ValueOf
要传入变量的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package main
import (
"fmt"
"reflect"
)
func main() {
var x = 1024
var intSlice = []uint64{256, 512, 1024}
pValueX := reflect.ValueOf(&x)
pValueStr := reflect.ValueOf(&intSlice)
fmt.Println("pValueX can set =", pValueX.CanSet())
fmt.Println("pValueStr can set =", pValueStr.CanSet())
valueElemInt := reflect.ValueOf(&x).Elem()
valueElemSlice := reflect.ValueOf(&intSlice).Elem()
fmt.Println("valueElemInt can set =", valueElemInt.CanSet())
fmt.Println("valueElemSlice can set =", valueElemSlice.CanSet())
}
|
1
2
3
4
|
pValueX can set = false
pValueStr can set = false
valueElemInt can set = true
valueElemSlice can set = true
|
Set、SetXXX#
通过反射修改变量的值,有两种方法,一种是使用 Set 方法,一种是使用 SetXXX() 方法,比如 SetString()、SetInt() 等。Go 语言反射修改变量语法:
1
|
reflect.ValueOf(&x).Elem().Set()
|
Go 语言反射修改变量语法:
1
|
reflect.ValueOf(&x).Elem().SetXXX()
|
1
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 main
import (
"fmt"
"reflect"
)
func main() {
var x = 1024
var str = "HaiCoder"
xValue := reflect.ValueOf(&x).Elem()
strValue := reflect.ValueOf(&str).Elem()
if xValue.CanSet() {
xValue.SetInt(10240)
fmt.Println("New xValue =", xValue)
}
if strValue.CanSet() {
strValue.SetString("kaicoder")
fmt.Println("New strValue =", strValue)
}
newXValue := reflect.ValueOf(20140)
newStrValue := reflect.ValueOf("hkhcoder")
if xValue.CanSet() {
xValue.Set(newXValue)
fmt.Println("New xValue =", xValue)
}
if strValue.CanSet() {
strValue.Set(newStrValue)
fmt.Println("New strValue =", strValue)
}
}
|
1
2
3
4
|
New xValue = 10240
New strValue = kaicoder
New xValue = 20140
New strValue = hkhcoder
|
Index#
Go 语言反射修改整个切片语法:
1
2
3
|
intSliceElemValue := reflect.ValueOf(&intSlice).Elem()
newVale := reflect.ValueOf(newSliceValue)
intSliceElemValue.Set(newVale)
|
Go 语言反射修改切片索引处的值语法:
1
2
3
|
intSliceValue := reflect.ValueOf(intSlice)
e := intSliceValue.Index(0)
e.SetInt(2560)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package main
import (
"fmt"
"reflect"
)
func main() {
var intSlice = []int{256, 512, 1024}
intSliceElemValue := reflect.ValueOf(&intSlice).Elem()
if intSliceElemValue.CanSet() {
newSliceValue := []int{2560, 5120, 10240}
newVale := reflect.ValueOf(newSliceValue)
intSliceElemValue.Set(newVale)
fmt.Println("NewSliceVal =", intSlice)
}
intSliceValue := reflect.ValueOf(intSlice)
e := intSliceValue.Index(0)
if e.CanSet() {
e.SetInt(2570)
fmt.Println("NewVal =", intSliceValue)
}
}
|
1
2
|
NewSliceVal = [2560 5120 10240]
NewVal = [2570 5120 10240]
|
Name、Type、Tag#
在 Golang 中,通过反射的 reflect.TypeOf()
获得反射的对象信息后,如果是结构体类型,可以通过反射值对象(reflect.Type)的 NumField() 和 Field() 方法获得结构体成员的详细信息。
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 main
import (
"fmt"
"reflect"
)
type Person struct {
Name string `json:name`
Age int `json:age`
}
func main() {
person := Person{
Name: "HaiCoder",
Age: 109,
}
personType := reflect.TypeOf(person)
for i := 0; i < personType.NumField(); i++ {
// 获取每个成员的结构体字段类型
fieldType := personType.Field(i)
fmt.Println("FiledName =", fieldType.Name, "Type =", fieldType.Type, "Tag =", fieldType.Tag)
}
filedName, isOk := personType.FieldByName("Name")
if isOk {
fmt.Println("FiledName =", filedName.Name, "Type =", filedName.Type, "Tag =", filedName.Tag)
} else {
fmt.Println("Filed Name not exist")
}
}
|
1
2
3
|
FiledName = Name Type = string Tag = json:name
FiledName = Age Type = int Tag = json:age
FiledName = Name Type = string Tag = json:name
|
Tag.Get#
在 Golang 中,通过反射除了可以解析结构体的字段信息,还可以解析结构体字段的 Tag 的具体信息,我们可以根据 Tag 名,获取 Tag 的具体描述。Go 语言解析结构体 Tag 语法:
1
2
3
|
personType := reflect.TypeOf(person)
fieldName, isOk := personType.FieldByName("Name")
jsobTagVal := fieldName.Tag.Get("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
|
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string `json:"name" bson:"Name"`
Age int `json:"age" bson:"Age"`
}
func main() {
person := Person{
Name:"HaiCoder",
Age:109,
}
personType := reflect.TypeOf(person)
fieldName, isOk := personType.FieldByName("Name")
if isOk{
jsonTag := fieldName.Tag.Get("json")
bsonTag := fieldName.Tag.Get("bson")
fmt.Println("Name Json Tag =", jsonTag, "Bson Tag =", bsonTag)
}else{
fmt.Println("No Name Field")
}
fieldAge, isOk := personType.FieldByName("Age")
if isOk{
jsonTag := fieldAge.Tag.Get("json")
bsonTag := fieldAge.Tag.Get("bson")
fmt.Println("Name Json Tag =", jsonTag, "Bson Tag =", bsonTag)
}else{
fmt.Println("No Age Field")
}
}
|
1
2
|
Name Json Tag = name Bson Tag = Name
Name Json Tag = age Bson Tag = Age
|
FieldByName(“Name”).String()#
在 Golang 中,通过反射的 reflect.ValueOf()
获得反射的对象信息后,如果是结构体类型,可以通过反射值对象(reflect.Value)的详细信息来获取结构体字段的值。Go 语言反射解析结构体字段值语法:
1
2
|
personValue := reflect.ValueOf(person)
nameValue := personValue.FieldByName("Name").String()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string `json:name`
Age int `json:age`
}
func main() {
person := Person{
Name:"HaiCoder",
Age:109,
}
personValue := reflect.ValueOf(person)
nameValue := personValue.FieldByName("Name").String()
ageValue := personValue.FieldByName("Age").Int()
fmt.Println("Name Value =", nameValue, "Age Value =", ageValue)
}
|
1
|
Name Value = HaiCoder Age Value = 109
|
SetString#
在 Golang 中,通过反射的 reflect.ValueOf()
获得反射的对象信息后,如果是结构体类型,可以通过反射的 Elem() 来修改字段值。Go 语言反射解析结构体字段值语法:
1
2
|
personNameValue := reflect.ValueOf(&person.Name)
personNameValue.Elem().SetString("haicoder")
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string `json:name`
Age int `json:age`
}
func main() {
person := Person{
Name: "HaiCoder",
Age: 109,
}
personNameValue := reflect.ValueOf(&person.Name)
personAgeValue := reflect.ValueOf(&person.Age)
personNameValue.Elem().SetString("haicoder")
personAgeValue.Elem().SetInt(100)
fmt.Println("Name Value =", person.Name, "Age Value =", person.Age)
}
|
Call#
在 Golang 中,通过反射的 reflect.ValueOf()
获得结构体实例信息后,可以通过反射实例的 MethodByName 获取相应的方法,然后调用 Call 调用方法。
通过反射的 MethodByName 获取的方法只能获取导出的方法,也就是首字母大写的方法。
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 main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
Score float64
}
// 定义一个结构体,并为结构体添加带参数和返回值的方法
func (s Student)IsPass(score float64)(isPass bool){
if s.Score >= score{
return true
}
return false
}
func main() {
var p = Student{
Name:"HaiCoder",
Age:10,
Score:99,
}
personValue := reflect.ValueOf(p)
IsPassFunc := personValue.MethodByName("IsPass")
args := []reflect.Value{reflect.ValueOf(100.0)}
res := IsPassFunc.Call(args)
fmt.Println("Pass =", res[0].Bool())
args = []reflect.Value{reflect.ValueOf(60.0)}
res = IsPassFunc.Call(args)
fmt.Println("Pass =", res[0].Bool())
}
|
1
2
|
Pass = false
Pass = true
|
nil#
在 Golang 中,通过反射的 reflect.ValueOf()
获得反射的对象信息后,该反射对象提供了一系列方法来进行零值和空值的判定。
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 main
import (
"fmt"
"reflect"
)
func main() {
// *int的空指针
var a *int
fmt.Println("var a *int:", reflect.ValueOf(a).IsNil())
// nil值
fmt.Println("nil:", reflect.ValueOf(nil).IsValid())
// *int类型的空指针
fmt.Println("(*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid())
// 实例化一个结构体
s := struct{}{}
// 尝试从结构体中查找一个不存在的字段
fmt.Println("不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid())
// 尝试从结构体中查找一个不存在的方法
fmt.Println("不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid())
// 实例化一个map
m := map[int]int{}
// 尝试从map中查找一个不存在的键
fmt.Println("不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}
|
1
2
3
4
5
6
|
var a *int: true
nil: false
(*int)(nil): false
不存在的结构体成员: false
不存在的结构体方法: false
不存在的键: false
|
并发编程#
go func#
在 Go 程序中使用 go 关键字为一个函数创建一个 goroutine。在 golang 中,创建 goroutine 有两种方法,分别为:使用普通函数创建和使用匿名函数创建。普通函数创建 goroutine 语法:
匿名函数创建 goroutine 语法:
1
2
3
|
go func( paramlist ){
//do something
}( paramlist2 )
|
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 main
import (
"fmt"
"time"
)
func printInfo(s string) {
for {
fmt.Println("Info =", s)
// 延时1秒
time.Sleep(time.Second)
}
}
func main() {
// 使用匿名函数,加上 go 关键字,创建 goroutine
go func(s string) {
for {
fmt.Println("Info =", s)
// 延时1秒
time.Sleep(time.Second)
}
}("嗨客网 Golang")
// 使用普通函数创建 goroutine
go printInfo("HaiCoder Golang")
time.Sleep(time.Duration(3) * time.Second)
}
|
1
2
3
4
5
6
|
Info = HaiCoder Golang
Info = 嗨客网 Golang
Info = 嗨客网 Golang
Info = HaiCoder Golang
Info = HaiCoder Golang
Info = 嗨客网 Golang
|
sync.WaitGroup#
Go 语言中要等待 goroutine 的结束,可以使用 sync.WaitGroup
相关的操作,首先,使用 wg.Add
方法增加需要等到的协程的数量,然后没执行完一个协程,使用 wg.Done
表明协程结束,最后使用 wg.Wait
等到所有的协程结束。
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 main
import (
"fmt"
"sync"
)
var (
wg sync.WaitGroup
)
func sum(num int) int {
defer wg.Done()
result := 0
for i := 1; i <= num; i++ {
result += i
}
fmt.Println("Sum =", result)
return result
}
func mul(num int) int {
defer wg.Done()
result := 1
for i := 1; i <= num; i++ {
result *= i
}
fmt.Println("Mul =", result)
return result
}
func main() {
wg.Add(2)
// 使用 sync.WaitGroup 等待多个协程结束
go sum(100)
go mul(10)
wg.Wait()
}
|
1
2
|
Mul = 3628800
Sum = 5050
|
sync.Mutex#
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 main
import (
"fmt"
"sync"
)
var(
// 计数器
count int
// count 变量的互斥锁
countMux sync.Mutex
)
// 返回当前计数器的值
func Count()int{
countMux.Lock()
defer countMux.Unlock()
return count
}
// 对计数器的值加一
func IncCount(){
countMux.Lock()
defer countMux.Unlock()
count++
}
func main() {
IncCount()
count := Count()
fmt.Println("Count =", count)
}
|
sync.RWMutex#
读写锁的使用必须保证 Lock 和 Unlock 成对出现,或者是 RLock 和 RUnlock 成对出现,在我们需要写数据时,需要使用成对的 Lock 和 Unlock,在我们需要读数据时,需要使用成对的 RLock 和 RUnlock。
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 main
import (
"fmt"
"sync"
)
var (
// 计数器
count int
// count 变量的读写互斥锁
countMux sync.RWMutex
)
// 返回当前计数器的值
func Count() int {
countMux.RLock()
defer countMux.RUnlock()
return count
}
// 对计数器的值加一
func IncCount() {
countMux.Lock()
defer countMux.Unlock()
count++
}
func main() {
// 使用 sync.RWMutex 读写锁加锁操作
IncCount()
count := Count()
fmt.Println("Count =", count)
}
|
chan#
chan 是 Go 语言中的一个核心类型,可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯。
chan 的本质是一个队列,且 chan 是线程安全的, 也就是自带锁的功能。
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 main
import (
"fmt"
"time"
)
type Person struct {
Name string
Age int
}
func main() {
person := Person{
Name: "HaiCoder",
Age: 18,
}
// 使用 Go 语言的 chan 发送一个结构体类型的数据
chanStruct := make(chan Person)
go func() {
chanStruct <- person
time.Sleep(time.Duration(1) * time.Second)
}()
//接受数据
msg := <-chanStruct
fmt.Println("chan Msg =", msg)
}
|
Go 语言中的 chan 也是一种系统资源,因此,我们不需要使用 chan 时,需要手动关闭管道。关闭管道,需要使用系统内置的 close 函数。
如果,我们向一个已经关闭的管道发送数据,那么程序会 pannic。Go 语言 chan 关闭语法:
单向chan#
Golang 中的 channel 默认是双向的,也就是既可以读也可以写,同时,我们还可以创建单向的 channel。单向 channel 也就是只能用于发送数据或者只能用于接收数据的 channel。Go 语言创建双向 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
|
package main
import (
"fmt"
)
// 接受一个只写 channel 作为参数
func producer(send chan<- int) {
for i := 0; i < 3; i++ {
send <- i
}
// 使用 close 关闭 channel
close(send)
}
// 接受一个只读 channel 作为参数
func consumer(receive <-chan int) {
for num := range receive {
fmt.Println("receive num =", num)
}
}
func main() {
// 创建一个双向channel
channel := make(chan int)
go producer(channel)
consumer(channel)
}
|
无缓冲chan#
无缓冲的通道是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送 goroutine 和接收 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
23
24
25
26
27
28
29
|
package main
import (
"fmt"
"time"
)
//使用无缓冲 channel 发送数据
func writeRoutine(intChan chan int) {
for i := 0; i < 3; i++ {
intChan <- i
time.Sleep(time.Duration(2) * time.Second)
fmt.Println("Send", i)
}
//关闭 channel
close(intChan)
}
// 从无缓冲 channel 读取数据
func readRoutine(intChan chan int) {
for val := range intChan{
fmt.Println("Receive =", val)
time.Sleep(time.Duration(10) * time.Second)
}
return
}
func main() {
// 创建无缓冲 channel
c := make(chan int)
go writeRoutine(c)
readRoutine(c)
}
|
带缓冲chan#
带缓冲的通道是一种在被接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求 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
|
package main
import (
"fmt"
"time"
)
//使用带缓冲 channel 发送数据
func writeRoutine(intChan chan int) {
for i := 0; i < 3; i++ {
intChan <- i
time.Sleep(time.Duration(2) * time.Second)
fmt.Println("Send", i)
}
//关闭 channel
close(intChan)
}
// 从带缓冲 channel 读取数据
func readRoutine(intChan chan int) {
for val := range intChan{
fmt.Println("Receive =", val)
time.Sleep(time.Duration(10) * time.Second)
}
return
}
func main() {
// 创建带缓冲 channel
c := make(chan int, 3)
go writeRoutine(c)
readRoutine(c)
}
|
channel超时处理#
在并发编程的通信过程中,经常会遇到超时问题,即向 channel 写数据时发现 channel 已满,或者从 channel 试图读取数据时发现 channel 为空。如果不正确处理这些情况,很可能会导致整个 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
|
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
quit := make(chan bool)
//新开一个协程
go func() {
for {
select {
case num := <-ch: //如果有数据,下面打印。但是有可能ch一直没数据
fmt.Println("received num = ", num)
case <-time.After(3 * time.Second): //上面的ch如果一直没数据会阻塞,那么select也会检测其他case条件,检测到后3秒超时
fmt.Println("TimeOut")
quit <- true //写入
}
}
}() //别忘了()
for i := 0; i < 3; i++ {
ch <- i
time.Sleep(time.Second)
}
<-quit //这里暂时阻塞,直到可读
fmt.Println("Over")
}
|
1
2
3
4
5
|
received num = 0
received num = 1
received num = 2
TimeOut
Over
|
select#
Go 语言中提供了 select 关键字,可以同时响应多个通道的操作。select 里的每个 case 语句必须是一个 IO 操作。
并且,select 后面并不带判断条件,而是直接去查看 case 语句。每个 case 语句都必须是一个面向 channel 的操作。当 select 里面有多个 case 都满足条件出发时,则 select 会随机选取一个 case 执行。只要有一个case执行,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
|
package main
import (
"fmt"
"time"
)
func pythonSender(ch chan string){
time.Sleep(10 * time.Second)
ch <- "Python Sender"
}
func golangSender(ch chan string){
time.Sleep(5 * time.Second)
ch <- "Golang Sender"
}
func main() {
chStr1 := make(chan string)
chStr2 := make(chan string)
go pythonSender(chStr1)
go golangSender(chStr2)
// 使用 select 监听多个 channel
select{
case str1 := <-chStr1:
fmt.Println(str1)
case str2 := <- chStr2:
fmt.Println(str2)
default:
fmt.Println("Run default case")
}
}
|
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
|
package main
import (
"fmt"
"time"
)
func pythonSender(ch chan string){
time.Sleep(100 * time.Second)
ch <- "Python Sender"
}
func golangSender(ch chan string){
time.Sleep(500 * time.Second)
ch <- "Golang Sender"
}
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
chStr1 := make(chan string)
chStr2 := make(chan string)
go pythonSender(chStr1)
go golangSender(chStr2)
// 使用 time.After 处理 select 超时
select{
case str1 := <-chStr1:
fmt.Println(str1)
case str2 := <- chStr2:
fmt.Println(str2)
case <-time.After(10 * time.Second):
fmt.Println("Timed out")
}
}
|
集合类#
Go 语言的数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。Go 语言数组定义:
1
|
var varName [count]Type
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package main
import (
"fmt"
)
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//使用下标索引的形式,可以访问数组元素
var arrHaiCoder [3]string
arrHaiCoder[0] = "Hello"
arrHaiCoder[1] = "嗨客网"
arrHaiCoder[2] = "HaiCoder"
fmt.Println("arrHaiCoder0 =", arrHaiCoder[0])
fmt.Println("arrHaiCoder1 =", arrHaiCoder[1])
fmt.Println("arrHaiCoder2 =", arrHaiCoder[2])
}
|
数组初始化#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package main
import (
"fmt"
)
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//创建数组时,即给数组设置初值
var arrHaiCoder [3]string = [3]string{"Hello", "嗨客网", "HaiCoder"}
var arrHaiCoder = [3]string{"Hello", "嗨客网", "HaiCoder"}
var arrHaiCoder = [...]string{"Hello", "嗨客网", "HaiCoder"}
var arrHaiCoder = [...]string{1:"Hello", 0:"嗨客网", 2:"HaiCoder"}
fmt.Println("arrHaiCoder0 =", arrHaiCoder[0])
fmt.Println("arrHaiCoder1 =", arrHaiCoder[1])
fmt.Println("arrHaiCoder2 =", arrHaiCoder[2])
}
|
数组遍历#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package main
import (
"fmt"
)
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//我们可以通过 for循环加索引的形式遍历数组
var arrHaiCoder = [3]string{"Hello", "嗨客网", "HaiCoder"}
for i := 0; i < len(arrHaiCoder); i++ {
fmt.Println(arrHaiCoder[i])
}
for index, value := range arrHaiCoder{
fmt.Println("Index =", index, "Value =", value)
}
}
|
数组比较#
Go 语言的数组的比较,是使用 == 的方式,如果数组的元素个数不相同,那么不能比较数组。长度与数组元素完全相同的两个数组
多维数组#
1
2
3
4
5
6
7
8
9
10
|
package main
import (
"fmt"
)
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//创建一个三行两列的二维数组
var arrHaiCoder = [3][2]string{{"Server", "Python"}, {"Server", "Golang"}, {"JavaScript", "Vue"}}
fmt.Println("arrHaiCoder =", arrHaiCoder)
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package main
import (
"fmt"
)
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//给定义好的三维数组赋值
var arrHaiCoder [2][2][2]int
for i := 0; i < 2; i++ {
for j := 0; j < 2; j++ {
for k := 0; k < 2; k++{
arrHaiCoder[i][j][k] = i * 100 + j * 10 + k
}
}
}
fmt.Println("arrHaiCoder =", arrHaiCoder)
}
|
切片的英文是 slice,Golang 中的切片是 数组 的一个引用,因此切片是引用类型,在进行传递时,遵守引用的传递机制。
切片的使用和数组类似,遍历切片、访问切片的元素和求切片的长度 len 与数组都一样。但切片的长度是可以变化的,不像数组是固定的,因此也可以说切片是一个可以动态变化的数组。
1
2
3
4
5
6
7
8
9
10
11
12
|
package main
import (
"fmt"
)
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//使用下标索引的形式,可以访问切片元素
var sliceHaiCoder = []string{"Hello", "嗨客网", "HaiCoder"}
fmt.Println("sliceHaiCoder0 =", sliceHaiCoder[0])
fmt.Println("sliceHaiCoder1 =", sliceHaiCoder[1])
fmt.Println("sliceHaiCoder2 =", sliceHaiCoder[2])
}
|
Go语言切片遍历总结#
Go 语言的切片的遍历,有两种方式,分别为:通过 for 循环与通过 for range 循环的方式。Go 语言 for 循环遍历切片:
1
2
3
|
for i := 0; i < len(slice); i++ {
//slice[i]
}
|
Go 语言 for range 循环遍历切片:
1
2
|
for index, value := range sliceHaiCoder{
}
|
append#
1
2
3
4
5
6
7
8
9
10
11
12
|
package main
import (
"fmt"
)
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//使用append函数,给切片添加一个切片
var sliceHaiCoder = []string{"Hello", "HaiCoder"}
var appendSlice = []string{"haicoder", "嗨客网"}
sliceHaiCoder = append(sliceHaiCoder, appendSlice...)
fmt.Println("sliceHaiCoder =", sliceHaiCoder)
}
|
Go语言切片添加元素#
1
2
3
4
5
6
7
8
9
10
|
package main
import (
"fmt"
)
func main() {
//使用append函数,配合切片索引实现在切片的index处添加元素
var sliceHaiCoder = []string{"Hello", "HaiCoder"}
sliceHaiCoder = append(sliceHaiCoder[:1], "嗨客网")
fmt.Println("sliceHaiCoder =", sliceHaiCoder)
}
|
1
|
sliceHaiCoder = [Hello 嗨客网]
|
1
2
3
4
5
6
7
8
9
10
11
|
package main
import (
"fmt"
)
func main() {
//使用append函数,在切片的index处插入切片
var sliceHaiCoder = []string{"Hello", "HaiCoder"}
var sliceCategory = []string{"Golang","Python","JavaScript"}
sliceHaiCoder = append(sliceHaiCoder[:1], append(sliceCategory, sliceHaiCoder[1:]...)...)
fmt.Println("sliceHaiCoder =",sliceHaiCoder)
}
|
1
|
sliceHaiCoder = [Hello Golang Python JavaScript HaiCoder]
|
Go语言切片删除元素总结#
删除索引 index 处的元素:
1
|
sliceHaiCoder = append(sliceHaiCoder[:index], sliceHaiCoder[index+1:]...)
|
删除索引 index 到 index2 处的元素:
1
|
sliceHaiCoder = append(sliceHaiCoder[:index], sliceHaiCoder[index2:]...)
|
删除切片的第一个元素语法:
1
|
sliceHaiCoder = sliceHaiCoder[1:]
|
删除切片的最后一个元素语法:
1
|
sliceHaiCoder = sliceHaiCoder[:len(sliceHaiCoder)-1]
|
删除切片前 N 个元素语法:
1
|
sliceHaiCoder = sliceHaiCoder[N:]
|
删除切片后 N 个元素语法:
1
|
sliceHaiCoder = sliceHaiCoder[:len(sliceHaiCoder)-N]
|
Go语言切片复制总结#
Go 语言的切片的复制使用内置的 copy 函数。Go 语言 copy 函数语法:
1
|
func copy(dst, src []Type) int
|
将切片 src 拷贝到切片 dst,返回拷贝成功的元素的个数。如果切片 src 的长度大于 dst 切片的长度,那么只会复制 dst 切片长度个元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package main
import (
"fmt"
)
func main() {
//使用内置的 copy 函数,复制切片
var sliceSrc = []string{"Hello", "HaiCoder"}
var sliceDst = []string{"嗨客网", "Python", "Golang"}
copy(sliceDst, sliceSrc)
fmt.Println("sliceDst =", sliceDst)
var sliceSrc1 = []string{"嗨客网", "Python", "Golang"}
var sliceDst1 = []string{"Hello", "HaiCoder"}
copy(sliceDst1, sliceSrc1)
fmt.Println("sliceDst1 =", sliceDst1)
}
|
1
2
|
sliceDst = [Hello HaiCoder Golang]
sliceDst1 = [嗨客网 Python]
|
Go语言多维切片遍历总结#
Go 语言的多维切片的遍历,有两种方式,分别为:通过 for 循环与通过 for range 循环的方式。Go 语言 for 循环遍历切片:
1
2
3
4
5
6
|
for i := 0; i < len(slice); i++ {
//slice[i]
for j := 0; j < len(slice[i]); j++{
//slice[i][j]
}
}
|
Go 语言 for range 循环遍历切片:
1
2
3
4
|
for index, value := range sliceHaiCoder{
for index2, value2 := range sliceHaiCoder[index]{
}
}
|
Go语言数组与切片区别教程#
Golang 中 数组 与 切片 的区别主要体现在以下几点:
- 切片是指针类型,数组是值类型
- 数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组)
- 数组只有长度一个属性,而切片比数组多了一个容量(cap)属性
- 切片的底层也是数组实现的
Go 语言 中 map 是一个 key(索引)和 value(值)形式的无序的集合,也可以称为关联数组或字典,Golang 中的 map 能够快速根据给定 key,找到对应的 value 的数据结构。
Golang 的 map 的 key 可以是任何可以使用 == 进行比较的 数据类型,比如 int、string、bool 等,value 可以是任意的类型。
map 是一个无序的数据结构,因此同一个 map,每次遍历获取的顺序很可能是不一致的。
Go语言map的创建总结#
Go 语言中 map 的创建有三种形式,分别为:先定义后使用 make 创建、直接使用 make 创建和初始化创建。先定义后使用 make 创建:
1
2
|
var mapName map[keyType]valueType
mapName = make(map[keyType]valueType, len)
|
直接使用 make 创建:
1
|
mapName := make(map[keyType]valueType, len)
|
初始化创建:
1
2
3
4
5
|
mapName := map[keyType]valueType{
"KEY1":"Value1",
"KEY2":"Value2",
"KEY3":"Value3"
}
|
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 main
import (
"fmt"
)
func main() {
var mapHaiCoder map[string]string
mapHaiCoder = make(map[string]string, 3)
mapHaiCoder["Server"] = "Golang"
mapHaiCoder["JavaScript"] = "Vue"
mapHaiCoder["Db"] = "Redis"
fmt.Println("mapHaiCoder =", mapHaiCoder)
mapHaiCoder := make(map[string]string, 3)
mapHaiCoder["Server"] = "Golang"
mapHaiCoder["JavaScript"] = "Vue"
mapHaiCoder["Db"] = "Redis"
fmt.Println("mapHaiCoder =", mapHaiCoder)
//创建map时,需指定map的key的类型和value的类型
herosMap := map[string]string{
"hero1":"宋江",
"hero2":"卢俊义",
"hero3":"吴用",
}
fmt.Println(herosMap)
}
|
Go语言map的赋值总结#
Go 语言中 map 的赋值有两种形式,分别为:先 make 后赋值和直接初始化赋值。先 make 后赋值语法:
1
2
3
4
|
var mapName = make(map[keyType]valueType, len)
mapName[Key1] = Vlaue1
mapName[Key2] = Vlaue2
mapName[Key3] = Vlaue3
|
初始化赋值语法:
1
2
3
4
5
|
mapName := map[keyType]valueType{
"KEY1":"Value1",
"KEY2":"Value2",
"KEY3":"Value3"
}
|
Go语言map遍历总结#
Go 语言中 map 的遍历只能使用 for range 的形式,for range 循环返回的第一个是 map 的 key,返回的第二个是 map 的 value。
使用 for range 遍历 map,如果我们只使用一个返回参数接受,那么返回的是 map 的 key。因此 map 是无序的,因此同一个 map,每次遍历获取的结果的顺序很可能是不一致的。for range 循环遍历 map 语法:
1
2
|
for key, value := range mapName{
}
|
for range 循环遍历 map key 语法:
1
2
|
for key := range mapName{
}
|
Go语言获取map元素教程#
Go 语言 中要获取 map 中的元素,除了使用 遍历 的方式,我们还可以使用 key 做为索引的形式来获取 map 指定 key 的元素。
根据 map 的 key 获取 map 的元素,返回两个返回值,第一个返回值是获取的值,如果 key 不存在,返回空值,第二个参数是一个 bool 值,表示获取值是否获取成功。
如果我们只使用一个值,接受 map 的返回值,那么返回的 map 的 key 对应的 value,如果我们需要判断一个 map 中的 key 是否存在,那么我们可以使用 _ 忽略返回的第一个值,然后判断返回的第二个 bool 值为 true 还是 false。
Go语言删除map元素总结#
Go 语言中要删除 map 中的元素,使用内置的 delete 函数。Go 语言 delete 语法:
如果 key 在 mapName 的 map 中,不存在,不会报错。
Go语言sync.Map教程#
Go 语言 中 map 如果在并发读的情况下是线程安全的,如果是在并发写的情况下,则是线程不安全的。Golang 为我们提供了一个 sync.Map 是并发写安全的。
Golang 中的 map 的 key 和 value 的 类型 必须是一致的,但 sync.Map 的 key 和 value 不一定是要相同的类型,不同的类型也是支持的。
Go 语言 sync.Map 无须初始化,直接声明即可使用。sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
Go语言sync.Map添加元素总结#
Go 语言中 sync.Map 的添加元素不是跟原生的 map 一样,使用 [] 的形式,而是使用内置的 Store 函数。Go 语言sync.Map Store 语法:
1
|
func (m *Map) Store(key, value interface{})
|
Go语言sync.Map获取元素总结#
Go 语言中 sync.Map 的获取元素不是跟原生的 map 一样,使用 [] 的形式,而是使用内置的 Load 函数。Go 语言 sync.Map Load 语法:
1
|
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
|
Go语言sync.Map删除元素总结#
Go 语言中 sync.Map 的删除元素是使用内置的 Delete 函数。Go 语言 sync.Map Delete 语法:
1
|
func (m *Map) Delete(key interface{})
|
Go语言sync.Map遍历元素总结#
Go 语言 中 sync.Map 的元素遍历,不可以使用 for 循环 或者 for range 循环,而是使用 Range 配合一个回调 函数 进行遍历操作。
通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
Go 语言中 sync.Map 的元素遍历,不可以使用 for 循环或者 for range 循环,而是使用 Range 配合一个回调函数进行遍历操作。Go 语言 sync.Map Range 语法:
1
|
func (m *Map) Range(f func(key, value interface{}) bool)
|
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
|
package main
import (
"fmt"
"sync"
)
func walk1(key, value interface{}) bool {
fmt.Println("Key =", key, "Value =", value)
return true
}
func walk2(key, value interface{}) bool {
fmt.Println("Key =", key, "Value =", value)
return false
}
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//使用 sync.Map Range 遍历元素
var mapHaiCoder sync.Map
mapHaiCoder.Store("Server", "Golang")
mapHaiCoder.Store("JavaScript", "Vue")
mapHaiCoder.Store("Db", "Redis")
mapHaiCoder.Range(walk1)
mapHaiCoder.Range(walk2)
}
|
1
2
3
4
|
Key = Server Value = Golang
Key = JavaScript Value = Vue
Key = Db Value = Redis
Key = JavaScript Value = Vue
|
Go语言sync.Map LoadOrStore教程#
Go 语言 中 sync.Map 的 LoadOrStore 函数 表示,如果我们获取的 key 存在,那么就返回 key 对应的元素,如果获取的 key 不存在,那么就返回我们设置的值,并且将我们设置的值,存入 map。
列表是一种非连续的存储容器,由多个节点组成,节点通过一些 变量 记录彼此之间的关系,列表有多种实现方法,如单链表、双链表等。
在 Go 语言 中,列表使用 container/list 包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作。
Golang 中的列表可以存储任意 数据类型 的值,列表的初始化有两种方式,
使用 list.New 初始化列表语法:
使用 var 初始化列表:
1
|
var listName = list.List
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package main
import (
"container/list"
"fmt"
)
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//通过 list.New 创建列表
listHaiCoder := list.New()
listHaiCoder.PushBack("Hello")
listHaiCoder.PushBack("HaiCoder")
fmt.Println(listHaiCoder)
var listHaiCoder2 list.List
listHaiCoder2.PushBack("Hello")
listHaiCoder2.PushBack("HaiCoder")
fmt.Println(listHaiCoder2)
}
&{{0xc0000744b0 0xc0000744e0 <nil> <nil>} 2}
{{0xc000074570 0xc0000745a0 <nil> <nil>} 2}
|
Go语言列表list插入元素总结#
Golang 的列表元素的插入有四种情景,分别为:在指定元素前插入、在指定元素后插入、在列表头部插入和在列表尾部插入。在列表指定元素前插入语法:
1
|
InsertBefore(v interface {}, mark * Element) *Element
|
在列表指定元素后插入语法:
1
|
InsertAfter(v interface {}, mark * Element) *Element
|
在列表头部插入语法:
1
|
PushFront(v interface{}) *Element
|
在列表尾部插入语法:
1
|
PushBack(v interface{}) *Element
|
Go语言列表list插入列表总结#
在一个列表中插入另一个列表,只支持两种情况,分别为:在头部插入列表和在尾部插入列表。在头部插入列表语法:
1
|
PushFrontList(other *List)
|
在尾部插入列表语法:
1
|
PushBackList(other *List)
|
Go语言列表list删除元素总结#
Golang 的列表的删除元素使用 remove 函数,删除的元素不能为空,如果为空,会报异常。列表删除元素Remove 语法:
1
|
Remove(e *Element) interface{}
|
Go语言列表list遍历总结#
Golang 的列表的遍历分为正序遍历和倒叙遍历,正序遍历就是从链表的头元素遍历到尾元素,倒叙遍历就是从链表的尾元素遍历到链表的头元素。Go 语言列表正序遍历语法:
1
2
3
|
for i := lis.Front(); i != nil; i = lis.Next() {
fmt.Println(i.Value)
}
|
Go 语言列表倒叙遍历语法:
1
2
3
|
for i := listHaiCoder.Back(); i != nil; i = i.Prev() {
fmt.Println(i.Value)
}
|
Go语言列表list元素移动教程#
Golang 的 列表 元素的移动有两种情景,分别为:将指定元素移动到另一元素的前面和将 指定元素移动到另一元素的后面 。
如果将指定元素移动到另一元素的前面中的指定元素本来就在另一元素的前面,那么列表不会做任何的改动,或者如果指定元素不是列表中的元素,列表也不会做任何改动。
1
2
|
MoveBefore(e, mark *Element)
MoveAfter(e, mark *Element)
|
参数
参数 |
描述 |
e |
要移动的元素。 |
mark |
移动元素的基准元素。 |
Go语言列表list元素移动总结#
如果要移到最前面的元素本来就在列表的最前面,那么列表不会做任何的改动,或者如果指定元素不是列表中的元素,列表也不会做任何改动。移到列表最前语法:
1
|
MoveToFront(e *Element)
|
如果要移到最后面的元素本来就在列表的最后面,那么列表不会做任何的改动,或者如果指定元素不是列表中的元素,列表也不会做任何改动。移到列表最后语法:
Go语言列表list获取元素总结#
Golang 的列表元素的获取可以使用内置的 Front 函数获取头结点,使用 Back 函数获取尾结点,使用 Prev 获取前一个结点,使用 Next 获取下一个结点。获取列表头结点语法:
获取列表尾结点语法:
获取上一个结点语法:
获取下一个结点语法:
Go语言列表list长度总结#
Golang 的列表长度的获取使用列表内置的 Len 函数。获取列表长度语法:
Go语言nil特性#
- nil 标识符是不能比较的
- nil 不是关键字或保留字
- 不同类型 nil 的指针是一样的
- 不同类型的 nil 是不能比较的
- 两个相同类型的 nil 值也可能无法比较
- nil 是常见引用类型的零值
Go语言new与make区别教程#
Go 语言 中 new 和 make 是两个内置 函数,主要用来创建并分配内存。Golang 中的 new 与 make 的区别是 new 只分配内存,而 make 只能用于 slice、map 和 channel 的初始化。
new和make主要区别#
- make 只能用来分配及初始化类型为 slice、map、chan 的数据,而 new 可以分配任意类型的数据。
- new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type。
- new 分配的空间被清零。make 分配空间后,会进行初始化。
字符串#
截取字符串#
如果我们要截取的字符串中包含中文字符串,首先需要将字符串转换成 rune 数组。Go 语言获取字符语法为:
Go 语言截取字符串,也叫 Go 语言字符串切片,其语法格式为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package main
import (
"fmt"
)
func main() {
//截取中文字符串时,首先需要将字符串转换成 rune 数组
str := "林志玲"
str1 := str[0:2]
strC := []rune(str)
str2 := strC[0:2]
fmt.Println("str1 =", string(str1), "str2 =", string(str2))
}
/str1 = �� str2 = 林志
|
拼接字符串总结#
Go 语言拼接字符串有五种方法,分别是:使用+号拼接、使用 sprintf 拼接、使用 join 函数拼接、使用 buffer.WriteString 函数拼接、使用 buffer.Builder 拼接。使用 + 号拼接语法:
使用 sprintf 拼接语法:
1
|
str = fmt.Sprintf("%s%d%s", s1, i, s2)
|
使用 join 函数拼接:
1
2
|
var str []string = []string{s1, s2}
s := strings.Join(str, "")
|
使用 buffer.WriteString 函数拼接:
1
2
3
4
5
|
var bt bytes.Buffer
bt.WriteString(s1)
bt.WriteString(s2)
//获得拼接后的字符串
s3 := bt.String()
|
使用 buffer.Builder 拼接:
1
2
3
4
|
var build strings.Builder
build.WriteString(s1)
build.WriteString(s2)
s3 := build.String()
|
常见的字符串拼接方式
-
- strings.Builder
- bytes.Buffer
strings.Builder 最快,bytes.Buffer 较快,+ 最慢
原理
- 字符串在 Go 语言中是不可变类型,占用内存大小是固定的,当使用 + 拼接 2 个字符串时,生成一个新的字符串,那么就需要开辟一段新的空间,新空间的大小是原来两个字符串的大小之和
- strings.Builder,bytes.Buffer 的内存是以倍数申请的
- strings.Builder 和 bytes.Buffer 底层都是 []byte 数组,bytes.Buffer 转化为字符串时重新申请了一块空间,存放生成的字符串变量,而 strings.Builder 直接将底层的 []byte 转换成了字符串类型返回
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
|
package main
import (
"bytes"
"fmt"
"strings"
)
func main() {
//使用 join 函数,实现拼接字符串
str1 := "Hello,"
str2 := "HaiCoder"
var str = []string{str1, str2}
strHaiCoder := strings.Join(str, "")
fmt.Println("strHaiCoder =", strHaiCoder)
//使用 buffer.WriteString 函数拼接字符串
var bt bytes.Buffer
bt.WriteString(str1)
bt.WriteString(str2)
strHaiCoder2 := bt.String()
fmt.Println("strHaiCoder2 =", strHaiCoder2)
//使用 buffer.Builder 函数拼接字符串
var build strings.Builder
build.WriteString(str1)
build.WriteString(str2)
strHaiCoder3 := build.String()
fmt.Println("strHaiCoder3 =", strHaiCoder3)
strHaiCoder4 := str1 + str2
fmt.Println("strHaiCoder4 =", strHaiCoder4)
strHaiCoder5 := fmt.Sprintf("%s %d %s", str1, 1024, str2)
fmt.Println("strHaiCoder5 =", strHaiCoder5)
}
strHaiCoder = Hello,HaiCoder
strHaiCoder2 = Hello,HaiCoder
strHaiCoder3 = Hello,HaiCoder
strHaiCoder4 = Hello,HaiCoder
strHaiCoder5 = Hello, 1024 HaiCoder
|
字符串长度总结#
在 Go 语言要想获取字符串长度有四种方法,分别为:使用 bytes.Count() 获取、使用 strings.Count() 获取、使用 len() 获取 和使用 utf8.RuneCountInString() 获取。
如果我们字符串中包含中文,推荐使用 utf8.RuneCountInString() 方法获取字符串长度。
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
|
package main
import (
"bytes"
"fmt"
"strings"
"unicode/utf8"
)
func main() {
//使用 bytes.Count() 获取字符串长度
strHaiCoder := "嗨客网(Hello, HaiCoder)"
strCount := bytes.Count([]byte(strHaiCoder), nil)
fmt.Println("strCount =", strCount) //结果输出了 21,但我们的字符串长度只有 20,所以使用 bytes.Count() 获取字符串长度需要减 1.
strCount2 := strings.Count(strHaiCoder, "")
fmt.Println("strCount2 =", strCount2)
strCount3 := len(strHaiCoder)
strCount4 := len([]rune(strHaiCoder))
fmt.Println("strCount3 =", strCount3, "strCount4 =", strCount4) //中文算3个
strCount5 := utf8.RuneCountInString(strHaiCoder)
fmt.Println("strCount5 =", strCount5) //当我们使用 utf8.RuneCountInString() 函数获取字符串长度时,一个中文和一个英文字符的长度都为 1.
}
|
golang中string底层是通过byte数组实现的。中文字符在unicode下占2个字节,在utf-8编码下占3个字节,而golang默认编码正好是utf-8。
- byte 等同于int8,常用来处理ascii字符
- rune 等同于int32,常用来处理unicode或utf-8字符
分割字符串总结#
在 Go 语言中,分割字符串我们可以分为几种情况,分别为:按空格分割、按字符串分割和按字符分割。Go 语言按空格分割字符串:
1
|
arr := strings.Fields(s)
|
Go 语言按字符串分割字符串语法:
1
|
arr := strings.Split(s,sep)
|
Go 语言按字符分割字符串语法:
1
|
arr := strings.FieldsFunc(s,f func(rune) bool)
|
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 main
import (
"fmt"
"strings"
)
func checkSpiltRune(r rune) bool {
if r > 97 {
return true
}
return false
}
func main() {
//使用 strings.Fields 函数,实现按空格分割字符串
strHaiCoder := "嗨客网 Hello HaiCoder"
strArr := strings.Fields(strHaiCoder)
fmt.Println("strArr =", strArr)
strHaiCoder2 := "Hello,HaiCoder Hello,World"
strArr2 := strings.Split(strHaiCoder2, "Hello")
fmt.Println("strArr2 =", strArr2)
strHaiCoder3 := "Hello,HaiCoder,Hello,World"
strArr3 := strings.FieldsFunc(strHaiCoder3, checkSpiltRune)
fmt.Println("strArr3 =", strArr3)
}
strArr = [嗨客网 Hello HaiCoder]
strArr2 = [ ,HaiCoder ,World]
strArr3 = [H ,Ha C ,H ,W]
|
字符串出现次数总结#
在开发过程中,很多时候我们有统计单个字符或者字符串在另一个字符串中出现次数的需求,在 Go 语言 中,统计字符串出现次数我们使用 Strings.count() 函数。Go 语言 Strings.count() 函数语法:
1
|
func Count(s, substr string) int
|
查找字符串总结#
在开发过程中,很多时候我们有在一个字符串中查找另一个字符串的需求,在 Go 语言中,在一个字符串中查找另一个字符串我们使用 Strings.Index() 系列函数。Go 语言 Strings.Index() 系列函数语法:
1
|
func Index(s, substr string) int
|
Strings.Index() 函数返回的是字符串第一个出现的位置,而 Strings.LastIndex() 函数返回的是字符串最后一次出现的位置。Go 语言 Strings.LastIndex() 系列函数语法:
1
|
func LastIndex(s, substr string) int
|
在 Go 语言中,在一个字符串中从开始查找另一个字符序列我们使用 Strings.IndexAny() 函数,从结尾往前查找我们使用 Strings.LastIndexAny() 函数。Go 语言 IndexAny() 函数语法:
1
|
func IndexAny(s, chars string) int
|
Go 语言 LastIndexAny() 函数语法:
1
|
func LastIndexAny(s, chars string) int
|
在 Go 语言中,在一个字符串中从开始查找一个字符我们使用 Strings.IndexByte() 函数,从结尾往前查找我们使用 Strings.LastIndexByte() 函数。Go 语言 IndexByte() 函数语法:
1
|
func IndexByte(s, chars string) int
|
Go 语言 LastIndexByte() 函数语法:
1
|
func LastIndexByte(s, chars string) int
|
在 Go 语言中,在一个字符串中从开始查找一个中文字符我们使用 Strings.IndexRune() 函数,从结尾往前查找我们使用 Strings.LastIndexRune() 函数。Go 语言 IndexRune() 函数语法:
1
|
func IndexRune(s string, r rune) int
|
Go 语言 LastIndexRune() 函数语法:
1
|
func LastIndexRune(s string, r rune) int
|
在 Go 语言中,在一个字符串中查找满足特定条件字符我们可以使用 Strings.IndexFunc() 函数。Go 语言 IndexFunc() 函数语法:
1
|
func IndexFunc(s string, f func(rune) bool) int
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package main
import (
"fmt"
"strings"
)
func checkRune(r rune) bool {
if r == 'G' || r == 'a' {
return true
}
return false
}
func main() {
//使用 Strings.IndexFunc() 函数,查找字符串中满足特定条件的字符
strHaiCoder := "I love Golang and I study Golang From HaiCoder"
indexFunc := strings.IndexFunc(strHaiCoder, checkRune)
fmt.Println("indexFunc =", indexFunc)
}
indexFunc = 7
|
字符串开头结尾总结#
在 Go 语言中,判断某个字符串是否以某个字符或者是否以某个字符串开头的函数为 strings.HasPrefix() 。Go 语言 strings.HasPrefix() 函数语法:
1
|
Strings.HasPrefix(s, prefix string) bool
|
在 Go 语言中,判断某个字符串是否以某个字符或者是否以某个字符串结尾的函数为 strings.HasSuffix() 。Go 语言 strings.HasSuffix() 函数语法:
1
|
func HasSuffix(s, suffix string) bool
|
大小写转换#
在开发过程中,很多时候我们需要将一个字符串首字母转成大写的需求,在 Go 语言中,将某个字符串的首字母转成大写的函数为 strings.ToTitle() 。Go 语言 strings.ToTitle() 函数语法:
1
|
func ToTitle(s string) string
|
在 Go 语言中,将某个字符串的大写字符转成小写使用的函数为 ToLower() 。Go 语言 ToLower() 函数语法:
1
|
func ToLower(s string) string
|
在 Go 语言中,将某个字符串的小写字符转成大写使用的函数为 ToUpper() 。Go 语言 ToUpper() 函数语法:
1
|
func ToUpper(s string) string
|
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 main
import (
"fmt"
"strings"
)
func main() {
//使用 Strings.IndexFunc() 函数,查找字符串中满足特定条件的字符
strHaiCoder := "i study Golang From HaiCoder"
strToTitle := strings.ToTitle(strHaiCoder)
fmt.Println(strToTitle)
strToLower := strings.ToLower(strHaiCoder)
fmt.Println(strToLower)
strToUpper := strings.ToUpper(strHaiCoder)
fmt.Println(strToUpper)
strTitle := strings.Title(strHaiCoder)
fmt.Println(strTitle)
}
I STUDY GOLANG FROM HAICODER
i study golang from haicoder
I STUDY GOLANG FROM HAICODER
I Study Golang From HaiCoder
|
去除字符串总结#
在 Go 语言中,去除字符串中空格的函数为 TrimSpace() 。Go 语言 TrimSpace() 函数语法:
1
|
func TrimSpace(s string) string
|
使用 TrimSpace() 函数的默认参数,只可以去除字符串的左右两边的空格,中间的空格无法删除。
在 Go 语言中,去除字符串中指定字符串的函数为 Trim() 。Go 语言 Trim() 函数语法:
1
|
func Trim(s string, cutset string) string
|
使用 Trim() 函数,不能去除字符串中间包含中指定字符串。
在 Go 语言中,去除字符串中左边指定字符串的函数为 TrimLeft() 。Go 语言 TrimLeft() 函数语法:
1
|
func TrimLeft(s string, cutset string) string
|
在 Go 语言中,去除字符串中右边指定字符串的函数为 TrimRight() 。Go 语言 TrimRight() 函数语法:
1
|
func TrimRight(s string, cutset string) string
|
在 Go 语言中,去除字符串中字符串前缀的函数为 TrimPrefix() 。Go 语言 TrimPrefix() 函数语法:
1
|
func TrimPrefix(s string, cutset string) string
|
在 Go 语言中,去除字符串中字符串后缀的函数为 TrimSuffix() 。Go 语言 TrimSuffix() 函数语法:
1
|
func TrimSuffix(s string, cutset string) string
|
在 Go 语言中,去除字符串中指定规则字符串的函数为 TrimFunc() 。Go 语言 TrimFunc() 函数语法:
使用 TrimFunc() 函数,只能去除字符串中左边和右边符合指定规则字符串,中间的不能去除。
1
|
func TrimFunc(s string, f func(rune) bool) string
|
1
|
func TrimLeftFunc(s string, f func(rune) bool) string
|
1
|
func TrimRightFunc(s string, f func(rune) bool) string
|
包含子串总结#
判断一个字符串是否是另一个字符串的子串,Golang 给我们提供了一个 Contains 函数。Go 语言 Contains() 函数语法:
1
|
func Contains(s, substr string) bool
|
判断一个字符是否在另一个字符串中,Golang 给我们提供了一个 ContainsRune 函数。Go 语言 ContainsRune() 函数语法:
1
|
func ContainsRune(s string, r rune) bool
|
判断一个字符序列中的任意一个字符是否是另一个字符串中,Golang 给我们提供了一个 ContainsAny 函数。Go语言 ContainsAny() 函数语法:
1
|
func ContainsAny(s, substr string) bool
|
遍历处理总结#
在 Go 语言中,提供了 strings.Map() 函数用于对一个字符串中的每一个字符都做相对应的处理的功能。Go 语言 Map() 函数语法:
1
|
func Map(mapping func(rune) rune, s string) string
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package main
import (
"fmt"
"strings"
)
func strEncry(r rune) rune {
if r >= 'A' && r <= 'Z' {
return r + 1
}
return r
}
func main() {
//使用 strings.Map() 函数,实现将一个字符串中的大写字符都后移一位
strHaiCoder := "嗨客网(HaiCoder)"
mapStr := strings.Map(strEncry, strHaiCoder)
fmt.Println("mapStr =", mapStr)
}
mapStr = 嗨客网(IaiDoder)
|
字符串比较总结#
在 Go 语言中,比较两个字符串是否完全相等,可以使用 ==。Go 语言使用 == 比较字符串相等语法:
如果相等,则返回 true,否则,返回 false。
在 Go 语言中,比较两个字符串大小是否完全相等,可以使用 strings.Compare。Go 语言使用 Compare 比较字符串相等语法:
比较字符串 a 和字符串 b 是否相等,如果 a > b,返回一个大于 0 的数,如果 a == b,返回 0,否则,返回负数。
1
|
func Compare(a, b string) int
|
在 Go 语言中,忽略大小写比较两个字符串是否相等,可以使用 strings.EqualFold。Go 语言使用 EqualFold 比较字符串相等语法:
如果相等,则返回 true,否则,返回 false。
1
|
func EqualFold(s, t string) bool
|
字符串重复总结#
在 Go 语言中,将一个字符串重复,我们可以使用 strings.Repeat() 函数 。Go 语言 strings.Repeat() 函数语法:
1
|
func Repeat(s string, count int) string
|
字符串全部替换总结#
在 Go 语言中,将某个字符串全部替换成新的字符串的需求,我们可以通过 strings.ReplaceAll() 函数来实现。Go 语言 strings.ReplaceAll() 函数语法:
1
|
func ReplaceAll(s, old, new string) string
|
在 Go 语言中,将某个字符串替换成新的字符串的需求,我们可以通过 strings.Replace() 函数来实现。Go 语言 strings.Replace() 函数语法:
1
|
func Replace(s, old, new string, n int) string
|
如果参数 n,传入的是负数,那么表明将字符串 s 中所有的 old 全部替换成 new。
为了完成某一功能的程序指令(语句)的集合,称为函数。Go 语言 的函数可以分为:自定义函数和系统函数。
Go 语言函数与其他语言函数最大的不同是,Go 语言的函数可以支持 返回任意多个值,而其他语言的函数一般只支持返回一个值。
Go 语言的函数也支持普通函数、匿名函数 和 闭包 三种形式。
Go 语言函数属于 “一等公民”,所以:
- 函数本身可以作为值进行传递。
- 支持匿名函数和闭包(closure)。
- 函数可以满足接口。
Go语言函数声明与定义总结#
在 Go 语言中,使用函数前,必须先声明与定义函数。Go 语言的函数由 关键字 func、函数名、参数列表、返回值、函数体和返回语句组成。Go 语言函数声明与定义语法:
1
2
3
4
|
func funcName(param1 param1Type, param2 param2Type, ...)(returnVal returnType){
//执行语句...
return valuelist
}
|
Go语言函数参数返回值总结#
Go 语言中函数可以不返回任何值,也可以返回一个或者多个值。Go 语言函数不返回任何值语法:
1
2
3
|
func funcName(param1, param2 paramType1, ...){
//执行语句...
}
|
Go 语言函数返回多个值语法:
1
2
3
4
|
func funcName(param1, param2 paramType1, ...)(returnType1, returnType2, ...){
//执行语句...
return 返回值列表
}
|
当返回值是多个时,需要将 returnType 的列表使用小括号括起来,不然语法会报错。调用函数时,也必须使用相对于的参数个数来接受返回值,如果不需要的返回值,我们可以使用匿名变量来接受保存。
Go 语言的函数的返回值我们可以显式的指定返回值的名称,在显式指定返回值名称的时候,如果相邻的几个返回值的类型相同,那么我们可以省略前几个返回值的类型,只需要写最后一个返回值的类型。Go 语言函数不返回任何值语法:
1
2
3
4
5
|
func funcName(param1, param2 paramType1, ...)(returnVal returnType){
// 执行语句...
returnVal = val
return
}
|
Go 语言函数返回多个值语法:
1
2
3
4
|
func funcName(param1, param2 paramType1, ...)(returnVal1, returnVal2 returnType ...){
// 执行语句...
return 返回值列表
}
|
在显式指定返回值名称的时候,如果相邻的几个返回值的类型相同,那么我们可以省略前几个返回值的类型,只需要写最后一个返回值的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package main
import (
"fmt"
"math"
)
func getPageCount(pageSize int)(isOk bool, pageCount int){
if pageSize <= 0{
return
}
pageCount = int(math.Ceil(float64(100/pageSize)))
isOk = true
return
}
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//Go语言的函数可以返回多个值
if isOk, pageCount := getPageCount(10); !isOk{
fmt.Println("Error")
}else{
fmt.Println("Ok, PageCount =", pageCount)
}
}
|
Go语言函数参数可变参数总结#
在 Go 语言中,函数的参数可以支持指定任意的个数与数据类型,这就是 Go 语言函数的可变参数。Golang 中函数的可变参数,必须是函数的最后一个参数,使用的形式是 ...
。Go 语言函数可变参数定义:
1
2
|
func funName(args ...paramType){
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package main
import (
"fmt"
)
func sum(args ...int)int{
sum := 0
for _, arg := range args{
sum += arg
}
return sum
}
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//Go语言函数可变参数,可以传入任意个数的参数
retSum1 := sum(10, 20)
retSum2 := sum(10, 20, 30, 50)
fmt.Println("retSum1 =", retSum1, ", retSum2 =", retSum2)
}
|
在 Go 语言中,函数的可变参数除了可以支持指定任意的个数,还可以支持任意的数据类型 。Go 语言函数可变参数定义语法:
1
2
|
func funName(args ...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
|
package main
import (
"fmt"
)
func haiPrint(args ...interface{}){
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "type is int")
case string:
fmt.Println(arg, "type is string")
case int64:
fmt.Println(arg, "type is int64")
case float64:
fmt.Println(arg, "type is float64")
default:
fmt.Println(arg, "type is unknown")
}
}
}
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//Go语言函数可变参数,可以传入任意个数与任意类型
haiPrint("Hello", "HaiCoder", 3, 100.1)
}
|
Go语言函数参数变量总结#
在 Go 语言中,函数也是一种类型,可以和其他数据类型一样保存在变量中。Go 语言函数变量语法:
1
2
3
4
|
func fun() {
}
var f func()
f = fun
|
Go 语言带参数的函数变量定义:
1
2
3
4
|
func fun(int)string {
}
var f func(int) string
f = fun
|
Go 语言的匿名函数可以作为一种类型被赋值给函数类型的变量,匿名函数也往往以变量方式传递。Go 语言匿名函数定义:
1
2
3
|
func(paramters)(returnvals){
//do something
}
|
Go 语言匿名函数调用定义:
1
2
3
|
func(paramters)(returnvals){
//do something
}(realParamters)
|
Go 语言的匿名函数可以直接赋值给变量, 也可以作为函数的参数,传递给函数。Go 语言匿名函数赋值给变量语法:
1
2
3
|
f = func(paramters)(returnvals){
//do something
}
|
Go 语言匿名函数做参数语法:
1
2
|
func walk(lis []int, callback func(int)){
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package main
import (
"fmt"
)
func print(num int){
fmt.Println("Num =", num)
}
func walk(lis []int, callback func(int)){
for _, i := range lis{
callback(i)
}
}
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
//Go语言匿名函数做参数,可以先定义出函数
slice := []int{1, 2, 3, 4}
walk(slice, print)
}
|
1
2
3
4
|
Num = 1
Num = 2
Num = 3
Num = 4
|
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
29
30
31
32
33
34
35
36
37
38
39
|
package main
import (
"fmt"
"strings"
)
func AddUpper() func(int) int {
var n int = 20
return func(x int) int {
n = n + x
return n
}
}
func makeSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
//Go语言闭包记忆效应,实现累加
f := AddUpper()
fmt.Println("闭包返回:", f(1))
fmt.Println("闭包返回:", f(2))
fmt.Println("闭包返回:", f(3))
//Go语言闭包,实现判断文件后缀
f1 := makeSuffix(".jpg")
fmt.Println("FileName =", f1("sea"))
fmt.Println("FileName =", f1("sun.jpg"))
}
//闭包返回: 21
//闭包返回: 23
//闭包返回: 26
//FileName = sea.jpg
//FileName = sun.jpg
|
Go语言defer总结#
在我们编写函数时,经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时释放资源,Go 语言设计者提供了 defer(延时机制)。
如果一个函数里面有多个 defer 语句,那么这些 defer 语句将会按照书写的逆序进行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。Go 语言 defer 语法:
1
2
3
4
|
func funcName(params)returnVal{
defer statement
//do something
}
|
Go 语言的 defer 语句 一般都是用来处理需要关闭的资源。如果同一个函数中,既有 defer 语句,同时也有 return 语句,那么 defer 语句会在 return 语句的后面执行。
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
|
package main
import (
"errors"
"fmt"
)
// 定义除数为0的错误
var errDiv = errors.New("division by zero")
func div(dividend, divisor int) (int, error) {
// 判断除数为0的情况并返回
if divisor == 0 {
return 0, errDiv
}
// 正常计算,返回空错误
return dividend / divisor, nil
}
func main() {
// 函数返回错误信息
if ret, err := div(2, 0); err != nil{
fmt.Println("Div err, Err =", err)
}else{
fmt.Println("Div ok, Ret =", ret)
}
}
|
Go语言panic总结#
在 Go 语言中,处理类似致命的错误的方法一般是通过 pannic 的方式来终止我们程序的执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package main
import (
"fmt"
)
func div(dividend, divisor int) int {
// 判断除数为0的情况并返回
if divisor == 0 {
panic("divisor is zero")
}
// 正常计算,返回空错误
return dividend / divisor
}
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
// 函数返回错误信息
ret := div(2, 0)
fmt.Println("Div ok, Ret =", ret)
}
|
Go语言recover总结#
我们希望我们程序在发生错误后,我们能够做一些处理,保证程序可以继续运行,那么这时候,我们就需要使用异常恢复,即 recover。Golang 中的 recover 一般都是配套 defer 一起使用。Go 语言 recover 语法:
1
2
3
4
5
|
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
|
我们在 defer 中,使用 if 判断 ,如果程序出现了异常,那么我们使用 recover 尝试恢复,并且打印异常信息。
panic和recover使用原则
- defer 需要放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。
- recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
- 多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用
Go语言panic和recover的关系
如果有 panic 但没有 recover,那么程序会宕机。如果有 panic 也有 recover,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行。
虽然 panic/recover 能模拟其他语言的异常机制,但并不建议在编写普通函数时也经常性使用这种特性。在 panic 触发的 defer 函数内,可以继续调用 panic,进一步将错误外抛,直到程序整体崩溃。
Go语言统计函数执行时长总结#
在 Go 语言中,统计函数的执行时长,最简单的方法就是在函数开始的时候计算时间,在函数运行结束时,计算函数的总运行时长。Go 语言统计函数执行时间语法:
1
2
3
4
5
|
func funcName(){
start := time.Now()
//Do something
elapsed := time.Since(start)
}
|
Go语言命令行参数继续总结#
flag 包提供的命令行参数解析的方式可以通过 key 和 value 的形式来获取。Go 语言解析命令行参数语法:
1
2
|
flag.StringVar(&user, "u", "root", "账号,默认为root")
flag.IntVar(&port, "P", 3306, "端口号,默认为3306")
|
也可以使用:
1
2
|
flag.String("u", "root", "账号,默认为root")
flag.Int("P", 3306, "端口号,默认为3306")
|
go语言init函数总结#
Go 语言程序每一个源文件都可以包含一个 init 函数,该函数会在 main 函数之前执行,被 Go 语言框架调用,也就是说 init 会在 main 函数之前被调用。
如果一个文件同时包含全局变量定义 ,init 函数和 main 函数,那么最先执行的是全局变量的定义,接着是 init 函数,最后执行的时候 main 函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package main
import (
"fmt"
)
func cfgFileName() string {
fmt.Println("Call Global Var init")
return "cfg.ini"
}
var CfgFile = cfgFileName()
func init() {
fmt.Println("Call init")
}
func main() {
//最先执行的是全局变量的定义,接着是 init 函数,最后执行的时候 main 函数。
fmt.Println("In main")
}
|
go语言接口特性#
在 Golang 中,接口有以下几个特点:
- 可以包含 0 个或者多个方法的签名。
- 只定义方法的签名、不包含实现。
- 实现接口不需要显式的声明、只需实现相应方法即可。
Go语言接口与其他语言接口#
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
package main
import (
"fmt"
)
// 在 Golang 中,一个类型可以实现多个接口
type MyWriter interface {
MyWriter(note string)
}
type MyCloser interface {
MyCloser()
}
type WriterCloser struct {
}
func (wc WriterCloser) MyWriter(note string) {
fmt.Println("Call WriterCloser MyWriter, Note =", note)
}
func (wc WriterCloser) MyCloser() {
fmt.Println("Call WriterCloser MyCloser")
}
// 在 Golang 中,一个接口也可以被多个类型实现
type SocketWriter struct {
}
type FileWriter struct {
}
func (s SocketWriter) MyWriter(note string) {
fmt.Println("Call SocketWriter MyWriter, Note =", note)
}
func (s FileWriter) MyWriter(note string) {
fmt.Println("Call FileWriter MyWriter, Note =", note)
}
func main() {
var writerCloser WriterCloser
writerCloser.MyWriter("Hello Golang")
writerCloser.MyCloser()
var socketWriter SocketWriter
var fileWriter FileWriter
socketWriter.MyWriter("Hello Golang")
fileWriter.MyWriter("Hello Python")
}
//Call WriterCloser MyWriter, Note = Hello Golang
//Call WriterCloser MyCloser
//Call SocketWriter MyWriter, Note = Hello Golang
//Call FileWriter MyWriter, Note = Hello Python
|
Golang类型断言总结#
因为在 Golang 中,接口变量的动态类型是变化的,有时我们需要知道一个接口变量的动态类型究竟是什么,这就需要使用类型断言,类型断言就是对接口变量的类型进行检查。Go 语言类型断言语法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package main
import (
"fmt"
)
func main() {
fmt.Println("嗨客网(www.haicoder.net)")
// 类型断言的判断,支持 switch 语句
var intvalue interface{}
intvalue = 99.8
switch intvalue.(type) {
case int:
fmt.Println("Type Int, Value =", intvalue.(int))
case float32:
fmt.Println("Type Float32, Value =", intvalue.(float32))
case float64:
fmt.Println("Type Float64, Value =", intvalue.(float64))
case string:
fmt.Println("Type string, Value =", intvalue.(string))
}
}
//Type Float64, Value = 99.8
|
在 Golang 中,将一个接口类型转换成另一个接口类型,或者将一个接口转换为另一个基本类型,都必须需要使用类型断言。Go 语言接口类型转换语法:
将接口 x 转换成 T 类型。 如果转换成功,返回转换成功后的值,即 value,ok 为 true。如果转换失败,value 为 零值,ok 为 false。Go 语言接口类型转换语法:
将接口 x 转换成 T 类型。 如果转换成功,返回转换成功后的值,即 value,如果转换失败,程序会 panic。
Golang接口嵌套总结#
在 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
|
package main
import (
"fmt"
)
type SReadWriter struct {
}
type MyReader interface {
ReaderFunc()
}
type MyWriter interface {
WriterFunc(str string)
}
type MyReadWriter interface {
MyReader
MyWriter
}
func (m SReadWriter) ReaderFunc() {
fmt.Println("Call ReaderFunc")
}
func (m SReadWriter) WriterFunc(str string) {
fmt.Println("Call WriterFunc Str =", str)
}
func main() {
// 必须实现嵌套的接口的所有方法,才算实现接口
var s interface{}
var readWriter SReadWriter
s = readWriter
readWriter.ReaderFunc()
readWriter.WriterFunc("Hello HaiCoder")
if reader, isOk := s.(MyReader); isOk {
fmt.Println("SReadWriter is type of MyReader, Reader =", reader)
}
if writer, isOk := s.(MyWriter); isOk {
fmt.Println("SReadWriter is type of MyReader, Writer =", writer)
}
if readWriter, isOk := s.(MyReadWriter); isOk {
fmt.Println("SReadWriter is type of MyReader, ReadWriter =", readWriter)
}
}
//Call ReaderFunc
//Call WriterFunc Str = Hello HaiCoder
//SReadWriter is type of MyReader, Reader = {}
//SReadWriter is type of MyReader, Writer = {}
//SReadWriter is type of MyReader, ReadWriter = {}
|
Go语言空接口教程#
Go 语言 中的空接口是 接口 类型的一种特殊的形式,即是一个没有任何 方法 的接口。因为,空接口没有任何方法,因此,我们可以说 Golang 中的任何 数据类型 都实现了空接口。空接口是任何类型的父接口。
使用空接口保存一个数据的过程会比直接用数据对应类型的 变量 保存稍慢。因此在开发中,应在需要的地方使用空接口,而不是在所有地方使用空接口。