Go简介
Go语言,Go 语言起源 2007 年,并于 2009 年正式对外发布,又称为Golang,是由Google三位顶尖大神开发的一种静态类型、编译型语言。它旨在提高多核处理器的编程效率,并且使得编程更加简单和高效。Go语言具有以下特点:
- 简洁性:Go语言的语法简单,易于学习,并且容易编写。
- 高性能:Go语言是编译型语言,编译后的程序运行速度快。
- 并发性:Go语言内置了并发编程的支持,通过goroutine和channel简化了并发编程。
- 垃圾回收:Go语言拥有自动垃圾回收机制,减轻了程序员的负担。
- 内存安全:Go语言在编译期间进行多种检查,确保内存安全。
- 丰富的标准库:Go语言拥有丰富的标准库,提供了许多实用的功能。
- 跨平台:Go语言支持跨平台编译,可以在多种操作系统上运行。
- 工具链:Go语言拥有强大的工具链,包括gofmt(代码格式化工具)、go test(测试工具)等。
Go语言适用于多种场景,包括但不限于:
- 网络编程和服务器端应用
- 分布式系统
- 微服务架构
- 云计算
- DevOps工具和自动化脚本
Go语言的这些特性使其成为现代软件开发中非常受欢迎的选择之一。
注:因为go官网golang.org在国内打不开,可以使用godoc -http=:8080在自己本地的8080端口开一个一样的官方进行浏览
Go的基础结构与基本数据类型
文件名、关键字与标识符
如果Go文件的文件名由多个部分组成,则使用下划线 _ 对它们进行分隔,如 scanner_test.go 。文件名不包含空格或其他特殊字符。
注意:_ 本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。
在编码过程中,你可能会遇到没有名称的变量、类型或方法。虽然这不是必须的,但有时候这样做可以极大地增强代码的灵活性,这些变量被统称为匿名变量。
Go 代码中会使用到的 25 个关键字或保留字
| break | default | func | interface | select |
|---|---|---|---|---|
| case | defer | go | map | struct |
| chan | else | goto | package | switch |
| const | fallthrough | if | range | type |
| continue | for | import | return | var |
Go 语言还有 36 个预定义标识符
其中包含了基本类型的名称和一些基本的内置函数
| append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
|---|---|---|---|---|---|---|---|---|
| copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
| int32 | int64 | iota | len | make | new | nil | panic | uint64 |
| println | real | recover | string | true | uint | uint8 | uintptr |
学习笔记
因为go官网golang.org在国内打不开,可以使用godoc -http=:8080在自己本地的8080端口开一个一样的官方进行浏览
项目结构
根据Go约定,gopath目录下应包含三个文件夹:
- bin:存放编译后的可执行文件
- pkg:存放编译后生成的包文件,一般是以
.a为后缀的文件 - src:存放项目的源码
包别名
在导入包时可以为其取别名,例如给”fmt“取别名为std:import std "fmt",此外有一个特别用法,就是别名取为.,这样在调用该包函数时前面无需写包名,被称为省略调用,如下:
import . "fmt"
func main(){
// 省略fmt包名调用
Println("Hello World!")
}
当然,省略调用不能和别名同时使用,不然别名就没有意义了
访问权限
在Golang中根据约定函数名首字母小写即为private,函数名首字母大写即为public
go基本类型
- bool:布尔型,1字节,只有true和false,不能用数字代表
- int/uint:整型,根据运行平台可能为32或64位
- int8/uint8:8位整型,1字节
- byte:字节型(uint8的别名,无符号8位整型)
- rune:int32的别名
- float/float64:4/8字节,精确到小数点后7/15位
- 复数:complex64/complex128,长度:8/16字节
- 足够保存指针的32位或64位整型:uintptr
- 其他值类型:array,struct,string
- 引用类型:slice(切片),map,chan(通道)
- 接口类型:interface
- 函数类型:func
变量声明
// 全局变量
var a int = 1
var b int
var c = 1
d := 1
var (e = 1, f = 2)
// 局部变量
func main(){
var a,b,c,d = 1,2,3,4
x,y,z := 1,2,3
}
var a int = 1
b := string(a)
fmt.Println(b) // 输出A
// 如果想要输出字符串的65,需要使用包“strconv”
b := strconv.Itoa(a)
fmt.Println(b) // 输出字符串65
c := strconv.AtoI(b) // 从字符串转回数字
常量
一般规范中定义常量使用全部大写字母来定义,但是由于golang规则中首字母大写表示可以被外部包调用,如果想声明私有常量一般会用_或小写字母c开头,如:
const _MAX = 10
const cNAME = "JOJO"
常量的右侧只能是常量或常量表达式,使用函数也只能是内置的函数,如果不赋值将使用上一行的值
const (
A = "123"
B = len(a)
C
D
)
因此上述代码中a为“123”,b为3,c和d也都为3
注意:使用上一行的值时要保证每行声明的常量个数要一样
const (
A, B = 1, 2
C // 这是不行的,与上一行个数不一致
C, D // 这个可以,c为1,d为2
)
在Golang中,有iota作为常量的计数器,初始为0,每定义一个常量iota就会增加1,同时每遇到const时iota都会变为0,因此枚举应写在一个常量组中
运算符

<<运算符将一个数左移 n 位,相当于将该数乘以$2^n$;>>运算符将一个数右移 n 位,相当于将该数除以$2^n$。
在golang中,++和--是作为语句存在,而不像其他语言中作为表达式,因此不能放在等号右侧,只能作为单独的一行
控制语句
分支
golang中的 if 后面没有小括号(),直接用空格分隔if关键字与条件,类似python,但是后面依旧是在同一行接左大括号,左大括号不能换行,否则编译错误。并且可以在写条件的部分声明新变量,此变量为局部变量,如果与外部变量同名则会隐藏外部变量,使用定义的局部变量,定义变量与后面条件用分号;分隔
switch:golang中的switch不需要每个case都写break,找到符合条件的case后会自动break跳出整个switch,如果你想要它继续向下验证可以使用fallthrough显式声明,并且golang中的switch也有不同形式
// 第一种
a := 1
switch a {
case 0:
fmt.Println("a=0")
case 1:
fmt.Println("a=1") // 只会输出a=1
default:
fmt.Println("None")
}
// 第二种
a := 1
switch {
case a >= 0:
fmt.Println("a>=0") // 只会输出a>=0
case a >= 1:
fmt.Println("a>=1")
default:
fmt.Println("None")
}
// 使用 fallthrough
a := 1
switch {
case a >= 0:
fmt.Println("a>=0") // 会输出a>=0
fallthrough
case a >= 1:
fmt.Println("a>=1") // 也会输出a>=0
default:
fmt.Println("None")
}
// 局部变量a
switch a := 1; {
case a >= 0:
fmt.Println("a>=0") // 会输出a>=0
fallthrough
case a >= 1:
fmt.Println("a>=1") // 也会输出a>=0
default:
fmt.Println("None")
}
循环
golang中的循环只有for一种,但是这里的for更多样,如下:
// 第一种
for {
a++
if a > 3 {
break
}
}
// 第二种
for a < 3 {
a++
}
// 第三种
for a := 0; a < 3; a++ {
fmt.println(a)
}
break,continue,goto
go中的break和continue除了在其他语言中的用法外还可以配合标签使用
LABLE:
for {
for i := 0; i < 3; i++ {
if i > 1 {
break LABLE // 直接跳出LABLE处的循环
}
}
}
// go to
for {
for i := 0; i < 3; i++ {
if i > 1 {
goto LABLE // 直接跳到LABLE处开始执行
}
}
}
LABLE:
fmt.Println("goto 到此处")
continue也是同理,带上标签就是跳过标签处的当前这次循环
数组
go中的数组为值类型,在使用时是对数组进行拷贝,并不是像其他语言那样是引用类型指向同一个地址
数组之间可以使用==和!=进行比较,但是不能使用<和>,并且类型不同也是不能进行比较
var a [2]int // 类型为 长度为2的整型
var a [1]int // 类型为 长度为1的整型,两个数组类型不同
// 前19个元素为0,第20个元素为1
a := [20]int{19: 1}
// 已知数组内容可以用 ... 省略数组元素个数
a := [...]int{1,2,3,4,5}
a := [...]int{19: 1} // 这样也是可以的,和上面的效果一样
指向数组的指针和指针数组
// 指向数组的指针
a := [...]int{3: 1}
var p *[4]int = &a // p的值为 &[0,0,0,1],就是打印a的值前面加上了&,表示取这个数组的地址
// new
p := new([5]int) // 使用new这种形式得到的依旧是指针,p的值为 &[0,0,0,0,0]
// 指针数组
x, y = 1, 2
a := [...]*int{&x, &y} // 是一个指针类型的数组,里面存放了x和y的地址
go中也是依然有多维数组,也是只能省略前面不能省略后面
a := [...][3]{{1,1,1},{2,2,2}}
用golang实现一下经典的冒泡排序
// 冒泡排序
a := [...]int{5, 2, 6, 3, 9}
fmt.Println("排序前:", a)
num := len(a) // 提前计算长度免得每次循环都要重新计算浪费资源
for i := 0; i < num; i++ {
for j := 0; j < num; j++ {
if a[i] < a[j] {
a[i], a[j] = a[j], a[i] // 真的方便,而且不用再定义一个temp
}
}
}
fmt.Println("排序后", a)
切片Slice
切片为引用类型,其作为变长数组的替代方案,可以关联底层数组的局部或全部,可以直接创建或从底层数组获取生成
如果多个slice指向相同的底层数组,其中一个的值改变会影响全部
声明切片和数组类似,只不过中括号里是空的
var s []int // 打印出来是 []
slice从数组中取元素
a := [10]int{0,1,2,3,4,5,6,7,8,9}
s := a[5] // 取索引为5的值,打印出来是 5
s := a[5: 10] // 范围左闭右开,即取索引5到9的数据,打印出来是 [5,6,7,8,9]
s := a[5:] // 结果和上面一样
s := a[:5] // 取前五个元素,依然是左闭右开,打印出来是 [0,1,2,3,4]
切片比较正式的创建是使用make创建
make([]T, len, cap) // len表示存储元素的个数,cap表示容量,cap可以省略,省略代表cap和len的值一样
补充:函数传参修改slice时推荐使用返回
package main
import "fmt"
func Keng(s []int) []int {
s = append(s, 3)
return s
}
func main() {
s := make([]int, 0)
fmt.Println(s)
s = Keng(s)
fmt.Println(s)
}
/* 输出结果
[]
[3]
*/
make时slice的长度和容量都为0,为了避免空间浪费可以使用返回的形式对其重新赋值
Reslice
Reslice时索引以被slice的切片为准
索引不可以超过被slice的切片的容量cap0值
索引越界不会导致底层数组的重新分配而是引发错误
func main() {
a := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'}
sa := a[2:5]
fmt.Println(string(sa)) // cde
fmt.Println(len(sa), cap(sa)) // 3 9
sb := sa[3:5]
fmt.Println(string(sb)) // fg
}

Append
可以在glice尾部追加元素
可以将一个slice追加在另一个slice尾部
func main() {
s1 := make([]int, 3, 6)
fmt.Printf("%p\n", s1) // 0xc00000e3f0
s1 = append(s1, 1, 2, 3)
fmt.Printf("%v %p\n", s1, s1) // [0 0 0 1 2 3] 0xc00000e3f0
s1 = append(s1, 1, 2, 3)
fmt.Printf("%v %p\n", s1, s1) // [0 0 0 1 2 3 1 2 3] 0xc000016180
}
如果最终长度未超过追加到slice的容量则返回原始slice
如果超过追加到的slice的容量则将重新分配数组并拷贝原始数据
func main() {
a := []int{1, 2, 3, 4, 5}
s1 := a[2:5]
s2 := a[1:3]
fmt.Println(s1, s2) // [3 4 5] [2 3]
s1[0] = 9
fmt.Println(s1, s2) // [9 4 5] [2 9]
}
slice指向数组,未超出容量时修改其中一个的值,其他的值也会跟着改变
func main() {
a := []int{1, 2, 3, 4, 5}
s1 := a[2:5]
s2 := a[1:3]
fmt.Println(s1, s2) // [3 4 5] [2 3]
s2 = append(s2, 1, 2, 3, 4, 5)
s1[0] = 9
fmt.Println(s1, s2) // [9 4 5] [2 3 1 2 3 4 5]
}
超过容量后会重新分配地址,修改其中一个的值,将不会影响到新分配的slice
copy
func main() {
s1 := []int{1, 2, 3, 4, 5}
s2 := []int{7, 8, 9}
copy(s1, s2)
fmt.Println(s1) // [7 8 9 4 5]
}
func main() {
s1 := []int{1, 2, 3, 4, 5}
s2 := []int{7, 8, 9}
copy(s2, s1)
fmt.Println(s2) // [1 2 3]
}
也可以只拷贝一部分
func main() {
s1 := []int{1, 2, 3, 4, 5}
s2 := []int{7, 8, 9}
copy(s2, s1[:2])
fmt.Println(s2) // [1 2 9]
}
也可以这样
func main() {
s1 := []int{1, 2, 3, 4, 5}
s2 := []int{7, 8, 9}
copy(s1[2:4], s2[:2])
fmt.Println(s1) // [1 2 7 8 5]
}
map
类似其它语言中的哈希表或者字典,以key-value形式存储数据
Key必须是支持==或!=比较运算的类型,不可以是函数、map或slice
Map查找比线性搜索快很多,但比使用索引访问数据的类型慢100倍
Map使用make0创建,支持:=这种简写方式
make([keyType]valueType,cap),cap表示容量,可省略
超出容量时会自动扩容,但尽量提供一个合理的初始值
使用len0)获取元素个数
键值对不存在时自动添加,使用delete0)删除某键值对
使用 for range 对map和slice进行迭代操作
var m map[int]string = make(map[int]string) // 声明map
func main() {
m := make(map[int]string)
m[1] = "go"
a := m[1]
fmt.Println(a) // go
delete(m, 1)
a = m[1]
fmt.Println(a) // (输出的是空)
}
map的值为一个map
func main() {
var m map[int]map[int]string
m = make(map[int]map[int]string)
a, ok := m[2][1]
fmt.Println(a, ok) // false (表示map没有声明)
if !ok {
m[2] = make(map[int]string)
}
m[2][1] = "OKK"
a, ok = m[2][1]
fmt.Println(a, ok) // OKK true (表示map声明了)
}
迭代
// 迭代slice
for i,v := range slice {
// i是索引,v是slice的拷贝
}
// 迭代map
for i,v := range map {
// i是键,v是map的拷贝
}
v只是一个拷贝,对v操作不会影响slice(map)本身
func main() {
sm := make([]map[int]string, 5)
for _, v := range sm {
v = make(map[int]string, 1)
v[1] = "OK"
fmt.Println(v)
}
fmt.Println(sm)
}
/* 输出结果
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
[map[] map[] map[] map[] map[]]
*/
上面并不会影响切片map本身,如果想操作其本身,可以使用下面代码
func main() {
sm := make([]map[int]string, 5)
for i := range sm {
sm[i] = make(map[int]string, 1)
sm[i][1] = "OK"
fmt.Println(sm[i])
}
fmt.Println(sm)
}
/* 输出结果
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
[map[1:OK] map[1:OK] map[1:OK] map[1:OK] map[1:OK]]
*/
对map实现间接排序
func main() {
m := map[int]string{1: "a", 2: "b", 3: "c", 4: "d", 5: "e"}
s := make([]int, len(m))
i := 0
for k, _ := range m {
s[i] = k
i++
}
sort.Ints(s) // 对整型进行排序
fmt.Println(s)
}
迭代map取出的key顺序是随机的,将取出的key放入slice中,再对其排序可以实现间接对map的key进行排序
将map的key和value对调
func main() {
m := map[int]string{1: "a", 2: "b", 3: "c", 4: "d", 5: "e"}
fmt.Println(m) // map[1:a 2:b 3:c 4:d 5:e]
m2 := make(map[string]int)
for k, v := range m {
m2[v] = k
}
fmt.Println(m2) // map[a:1 b:2 c:3 d:4 e:5]
}
函数function
Go 函数 不支持 嵌套、重载和默认参数
但支持以下特性:
- 无需声明原型:不定长度变参、多返回值、命名返回值参数
- 匿名函数、闭包
定义函数使用关键字 func,且左大括号不能另起一行
函数也可以作为一种类型使用
// 不定长度变参 相当于拷贝一份并放入slice中 不定长度变参只能放在参数最后面
func A(b string, a ...int){
fmt.Println(a)
}
// 多返回值 命名返回值参数
func A() (a,b,c int){
a,b,c = 1,2,3 // a,b,c已经声明过了,这里可以直接赋值
return // 上面返回值处已经声明了要返回的a,b,c,这里可以省略
}
// 匿名函数
a := func(){
fmt.Println("Func")
} // 可以使用a()来调用这个函数
匿名函数不能作为顶级函数,也就是不能作为最外层函数
闭包
func main() {
f := closure(10)
fmt.Println(f(1)) //
fmt.Println(f(2)) //
}
func closure(x int) func(int) int {
fmt.Printf("%p\n", &x) //
return func(y int) int {
fmt.Printf("%p\n", &x) //
return x + y
}
}
/* 输出结果
0xc000096068
0xc000096068
11
0xc000096068
12
*/
defer
defer的执行方式类似其它语言中的析构函数,在函数体执行结束后
按照调用顺序的相反顺序逐个执行
即使函数发生严重错误也会执行
支持匿名函数的调用
常用于资源清理、文件关闭、解锁以及记录时间等操作
通过与匿名函数配合可在return之后修改函数计算结果
如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer
时即已经获得了拷贝,否则则是引用某个变量的地址
Go 没有异常机制,但有 panic/recover 模式来处理错误
Panic 可以在任何地方引发,但recover只有在defer调用的函数中有效
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
上述代码打印结果为2 1 0,defer先进后出,为栈结构
defer与闭包一起使用
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}() // 自执行函数,如果把前面的匿名函数赋给变量a,就和a()一样了
}
上述代码执行结果为3 3 3,内层函数没有i,向外层寻找找到了i,拿到了i的引用也就是i的地址,循环结束时i为3,所以闭包调用i的都为3
panic与recover
func main() {
A()
B()
C()
}
func A() {
fmt.Println("func A")
}
func B() {
panic("panic in B")
}
func C() {
fmt.Println("func C")
}
/* 输出结果
func A
panic: panic in B
报错
*/
调用panic之后程序不会再执行后面的内容,这个时候就可以使用recover再回复回来,注:recover只有在defer调用的函数中有效
func main() {
A()
B()
C()
}
func A() {
fmt.Println("func A") // func A
}
func B() {
defer func() {
if err := recover(); err != nil {
fmt.Println("recover in B") // recover in B
fmt.Println(err) // panic in B
}
}()
panic("panic in B")
}
func C() {
fmt.Println("func C") // func C
}
调用recover之后将不会调用panic,打印err时会输出panic in B
疑问代码
以下代码输出结果不一致
func main() {
var fs = [4]func(){}
for i := 0; i < 4; i++ {
defer fmt.Println("defer i = ", i)
defer func() {
fmt.Println("defer_closure i = ", i)
}()
fs[i] = func() {
fmt.Println("closure i = ", i)
}
}
for _, f := range fs {
f()
}
}
/* 输出结果
closure i = 0
closure i = 1
closure i = 2
closure i = 3
defer_closure i = 3
defer i = 3
defer_closure i = 2
defer i = 2
defer_closure i = 1
defer i = 1
defer_closure i = 0
defer i = 0
*/
上述代码原来的输出结果如下
closure i = 4
closure i = 4
closure i = 4
closure i = 4
defer_closure i = 4
defer i = 3
defer_closure i = 4
defer i = 2
defer_closure i = 4
defer i = 1
defer_closure i = 4
defer i = 0
这是为什么,go发生了什么重大的改变吗?
结构struct
Go 中的struct与C中的struct非常相似,并且Go没有class
使用 type
支持指向自身的指针类型成员
支持匿名结构,可用作成员或定义成员变量
匿名结构也可以用于map的值
可以使用字面值对结构进行初始化
允许直接通过指针来读写结构成员
相同类型的成员可进行直接拷贝赋值
支持 ==与!=比较运算符,但不支持>或<
支持匿名字段,本质上是定义了以某个类型名为名称的字段
嵌入结构作为匿名字段看起来像继承,但不是继承
可以使用匿名字段指针
package main
import "fmt"
type person struct {
Name string
Age int
}
func main() {
a := person{
Name: "jojo",
}
a.Age = 19
fmt.Println(a)
A(a)
fmt.Println(a)
}
func A(per person) {
per.Age = 12
fmt.Println("func A", per)
}
/* 输出结果
{jojo 19}
func A {jojo 12}
{jojo 19}
*/
在函数A中只是一个值拷贝,可以使用指针来修改原值,变成引用
package main
import "fmt"
type person struct {
Name string
Age int
}
func main() {
a := person{"jojo", 19} // 也可以直接这样赋值
fmt.Println(a)
A(&a)
fmt.Println(a)
}
func A(per *person) {
per.Age = 12
fmt.Println("func A", per)
}
/* 输出结果
{jojo 19}
func A &{jojo 12}
{jojo 12}
*/
在go中哪怕取了地址,也可以像之前一样修改值,如下
a := &person{
Name: "jojo"
}
a.Name = "Dio" // 完全可以并且是正确的,这是go方便的地方
匿名结构
a := struct {
Name string
Age int
}{
Name: "jojo",
Age: 19,
}
go中的组合,对标继承的存在
type human struct{
Sex int
}
type teacher struct{
human
Name string
Age int
}
type student struct {
human
Name string
Age int
}
func main(){
a := teacher{Name: "joe",Age: 19,human: human{Sex: 0}}
b := student{Name: "joe",Age: 20,human: human{Sex: 1}}
a.Name = "joe2"
a.Age = 13
a.human.Sex = 10 // 可以这样
a.Sex = 100 // 也可以直接这样
fmt.Println(a,b)
}
方法method
Go 中虽没有class,但依旧有method
通过显示说明receiver来实现与某个类型的组合
只能为同一个包中的类型定义方法
Receiver可以是类型的值或者指针
不存在方法重载
可以使用值或指针来调用方法,编译器会自动完成转换
从某种意义上来说,方法是函数的语法糖,因为receiver其实就是方法所接收的第1个参数(Method Valuevs,Method Expression)
如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法
类型别名不会拥有底层类型所附带的方法
方法可以调用结构中的非公开字段
package main
import "fmt"
type A struct {
Name string
}
type B struct {
Name string
}
func main() {
a := A{}
a.Print() // A
fmt.Println(a.Name) // AA
b := B{}
b.Print() // B
fmt.Println(b.Name) // 输出是空,因为Name没有值
}
func (a *A) Print() {
a.Name = "AA"
fmt.Println("A")
}
func (b B) Print() {
b.Name = "BB"
fmt.Println("B")
}
类型别名也可以绑定方法
type TZ int
func main(){
var a TZ
a.Print() // TZ
}
func (a *TZ)Print(){
fmt.Println("TZ")
}
还可以这样
type TZ int
func main(){
var a TZ
a.Print() // Method Valuevs
(*TZ).Print(&a) // Method Expression
}
func (a *TZ)Print(){
fmt.Println("TZ")
}
/* 输出结果
TZ
TZ
*/
接囗interface
接口是一个或多个方法签名的集合
只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显示
声明实现了哪个接口,这称为 Structural Typing
接口只有方法声明,没有实现,没有数据字段
接口可以匿名嵌入其它接口,或嵌入到结构中
将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个
复制品的指针,既无法修改复制品的状态,也无法获取指针
只有当接口存储的类型和对象都为nil时,接口才等于nil
接口调用不会做receiver的自动转换
接口同样支持匿名字段方法
接口也可实现类似OOP中的多态
空接口可以作为任何类型数据的容器
package main
import "fmt"
type USB interface {
Name() string
Connecter
}
type Connecter interface {
Connect()
}
type PhoneConnecter struct {
name string
}
func (pc PhoneConnecter) Name() string {
return pc.name
}
func (pc PhoneConnecter) Connect() {
fmt.Println("Connected:", pc.name)
}
func main() {
a := PhoneConnecter{"PhoneConnecter"}
a.Connect()
Disconnect(a)
}
func Disconnect(usb USB) {
if pc, ok := usb.(PhoneConnecter); ok {
fmt.Println("Disconnect:", pc.name)
return
}
fmt.Println("Unknown decive")
}
/* 输出结果
Connected: PhoneConnecter
Disconnect: PhoneConnecter
*/
像Object一样,go中也可以看作所有结构都“继承”了空的接口
package main
import "fmt"
type empty interface{}
type USB interface {
Name() string
Connecter
}
type Connecter interface {
Connect()
}
type PhoneConnecter struct {
name string
}
func (pc PhoneConnecter) Name() string {
return pc.name
}
func (pc PhoneConnecter) Connect() {
fmt.Println("Connected:", pc.name)
}
func main() {
a := PhoneConnecter{"PhoneConnecter"}
a.Connect()
Disconnect(a)
}
func Disconnect(usb interface{}) {
switch v := usb.(type) {
case PhoneConnecter:
fmt.Println("Disconnect:", v.name)
default:
fmt.Println("Unknown decive")
}
}
/* 输出结果
Connected: PhoneConnecter
Disconnect: PhoneConnecter
*/
反射reflection
反射可大大提高程序的灵活性,使得interface{} 有更大的发挥余地
反射使用 TypeOf和 ValueOf函数从接口中获取目标对象信息
反射会将匿名字段作为独立字段(匿名字段本质)
想要利用反射修改对象状态,前提是interface.data是settable即 pointer-interface
通过反射可以“动态”调用方法
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) Hello() {
fmt.Println("hello world")
}
func main() {
u := User{1, "jack", 20}
Info(u)
}
func Info(o interface{}) {
t := reflect.TypeOf(o)
fmt.Println("type:", t.Name())
if k := t.Kind(); k != reflect.Struct {
fmt.Println("不合法的类型:", t.Kind())
return
}
v := reflect.ValueOf(o)
fmt.Println("Fields:")
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
val := v.Field(i).Interface()
fmt.Printf("%6s: %v = %v\n", f.Name, f.Type, val)
}
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("%6s: %v\n", m.Name, m.Type)
}
}
/* 输出结果
type: User
Fields:
Id: int = 1
Name: string = jack
Age: int = 20
Hello: func(main.User)
*/
/* Info(&u) 触发if报错的输出结果
type:
不合法的类型: ptr
*/
反射操作基本类型
func main() {
x := 123
v := reflect.ValueOf(&x)
v.Elem().SetInt(999)
fmt.Println(x) // 999
}
反射修改对象属性
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func main() {
u := User{1, "jack", 20}
Set(&u)
fmt.Println(u)
}
func Set(o interface{}) {
v := reflect.ValueOf(o)
if v.Kind() == reflect.Ptr && !v.Elem().CanSet() {
fmt.Println("类型不对")
return
} else {
v = v.Elem()
}
f := v.FieldByName("Name")
if !f.IsValid() {
fmt.Println("没找到这个字段")
return
}
if f.Kind() == reflect.String {
f.SetString("OK")
}
}
/* 输出结果
{1 OK 20}
*/
通过反射调用方法
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) Hello(Name string) {
fmt.Println("Hello:", Name, ",我是:", u.Name)
}
func main() {
u := User{1, "jack", 20}
v := reflect.ValueOf(u)
mv := v.MethodByName("Hello")
args := []reflect.Value{reflect.ValueOf("joe")}
mv.Call(args)
}
/* 输出结果
Hello: joe ,我是: jack
*/
并发concurrency
Channel
Channel是 goroutine沟通的桥梁,大都是阻塞同步的
通过 make 创建,close 关闭
Channel是引用类型
可以使用 for range 来迭代不断操作 channel
可以设置单向或双向通道
可以设置缓存大小,在未被填满前不会发生阻塞
func main() {
c := make(chan bool)
go func() {
fmt.Println("GO GO GO!") // GO GO GO!
c <- true
}()
<-c
}
func main() {
c := make(chan bool)
go func() {
fmt.Println("GO GO GO!")
c <- true
close(c)
}()
for v := range c {
fmt.Println(v)
}
}
/* 输出结果
GO GO GO!
true
*/
多CPU核数
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
c := make(chan bool, 10)
for i := 0; i < 10; i++ {
go Go(c, i)
}
for i := 0; i < 10; i++ {
<-c
}
}
func Go(c chan bool, index int) {
a := 1
for i := 0; i < 10000000; i++ {
a += 1
}
fmt.Println(index, a)
c <- true
}
另一种方法,同步任务组
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go Go(&wg, i)
}
wg.Wait()
}
func Go(wg *sync.WaitGroup, index int) {
a := 1
for i := 0; i < 10000000; i++ {
a += 1
}
fmt.Println(index, a)
wg.Done()
}
Select
可处理一个或多个channel 的发送与接收
同时有多个可用的 channel时按随机顺序处理
可用空的 select 来阻塞 main 函数
可设置超时
package main
import (
"fmt"
)
func main() {
c1, c2 := make(chan int), make(chan string)
o := make(chan bool, 2)
go func() {
for {
select {
case v, ok := <-c1:
if !ok {
o <- true
break
}
fmt.Println("c1", v)
case v, ok := <-c2:
if !ok {
o <- true
break
}
fmt.Println("c2", v)
}
}
}()
c1 <- 1
c2 <- "hi"
c1 <- 3
c2 <- "hello"
close(c1)
close(c2)
for i := 0; i < 2; i++ {
<-o
}
}
/* 输出结果
c1 1
c2 hi
c1 3
c2 hello
*/
坑
时间time的坑
time.format
