Go是google公司从2009年开始开发的一款静态、编译型,且自带垃圾回收和并发的编程语言
Go语言在多核并发上拥有原生的设计优势,实现是基于goroutine,类似于线程,但并不是线程,可以理解为虚拟线程,运行时参与调度goroutine,并将goroutine合理的分配到每个CPU中,最大限度的使用CPU资源,多个goroutine间使用channel进行通信。
1 路径说明
GOROOT //go安装目录
GOPATH //go项目目录
2 声明变量
自动会变量分配内存并初始化默认值
整型、浮点型默认值为0
字符串默认值空串
切片、函数、指针默认为nil
匿名变量不占用命名空间,不分配内存,用“_”表示
var age int
var age = 20
age := 22
3 数组与切片
数组是类型相同的元素集合,大小固定,以索引方式访问
切片是类型相同的元素集合,大小可变,可把数组转换为切片,切片没有删除的内置接口,需要利用切片本身,截取再拼接实现,使用make时,一定发送了内存分配操作,但给定起始位置的切片只是将新的切片结构指向已经分配好的内存区域,故不会发生内存分配,注意二者的长度和容量的区别
切片扩容机制,与切片的数据类型、原本切片的容量、所需要的容量都有关系,比较复杂。对于常见数据类型,在元素数量较少时,大致可以认为扩容是按照翻倍进行的。但具体情况需要具体分析
//array
var scores [100]int //自动初始化,默认为0
var ages = [10]int{12,23,45} //初始化部分元素,其他默认为0
//转为切片
s1 := scores[0:]
//slice
var sli = make([]int, 5)
//追加元素
sli = append(sli, 100)
4 指针
即是地址,获取变量地址,用&取得,并赋值给指针变量
age := 10
var ptr *int
ptr = &age
fmt.Printf("%T", ptr) //*int
str := new(string)
//或者new创建
name := "jackie"
str := new(string) //会自动分配内存地址,默认值为空串
str = &name
fmt.Printf("%T", str) //*string
5 字符串的不可变性
字符串是值类型,字符串的内容是不能修改(不能根据索引修改其中的某个字符内容),如果想改变,转切片修改,再string() 转换即可,
str := "angle"
fmt.Printf("type: %T, value: %s\n", str, str)
//字符串转为切片
bt := []byte(str)
fmt.Printf("type: %T, value: %s\n", bt, bt)
//byte类型是使用单引号,string类型是使用的双引号,否则会报:
//cannot use "k" (type string) as type byte in assignment
bt[0] = 'k'
fmt.Printf("type: %T, value: %s\n", bt, bt)
//输出
type: string, value: angle
type: []uint8, value: angle
type: []uint8, value: kngle
6 列表list
可快速增删的非连续空间容器,底层实现是双向链表,其成员并不限制类型统一
container/list包
l := list.New()
var l list.List
7 建立事物关联的容器map
底层是根据散列表hash实现,这就涉及一些基本问题,hash函数与冲突解决
//key 是string类型,value是int类型,make创建的默认值为0
var m = make(map[string]int)
v1 := m["a"]
fmt.Println(v1)
//判断key是否存在
v2, ok := m["b"]
fmt.Println(v2, ok)
m["c"] = 1
v3 := m["c"]
fmt.Println(v3)
//输出结果
0
0 false //false表示key不存在
1
8 函数
一等公民,支持多返回值
func func_name(参数列表,类型) (返回值列表,类型) {
...
return a, b
}
//声明函数变量
var f func()
f = func_name
f()
//匿名函数
f := func(a int) (a int) {
return abs(a)
}
f()
9 defer 延迟语句
延迟执行语句,在函数结束时执行,故适合处理资源释放工作,例如:
1 在并发加锁时,马上声明释放锁操作
2 文件句柄释放、关闭问题
执行顺序:多个defer语序,逆序执行
func a () {
fmt.Println("a")
}
func b () {
fmt.Println("b")
}
func main() {
defer a()
defer b()
}
//输出
b
a
10 宕机panic与恢复recover
panic:程序终止执行,其后的代码不会执行,会将堆栈和goroutine信息输出到控制台
但是在panic函数钱已经允许的defer语句依然会在宕机发生时执行
recover:防止程序崩溃,崩溃后继续执行
defer + recover 实现错误捕捉和恢复,实现try-catch功能
func a () {
fmt.Println("a")
}
func b () {
fmt.Println("b")
}
func main() {
defer a()
panic("panic")
defer b() //unreachable code
}
//输出
a
panic: panic
goroutine 1 [running]:
main.main()
c:/Users/Administrator/Desktop/test.go:17 +0x5c
exit status 2
recover捕获异常,程序继续执行
func getByIndex(i int) int {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
var ap [10]int
return ap[i]
}
func main() {
fmt.Println("main start")
getByIndex(10)
fmt.Println("main end")
}
//输出
main start
runtime error: index out of range [10] with length 10
main end
11 结构体
实例化new 或者 &
定义是内存布局的描述,只有在实例化时才会分配内存
结构体可以嵌套
匿名结构体
组合思想描述对象特性
//声明结构体
type Stu struct {
name string
age int
score int
}
func main() {
var s1 = new(Stu)
var s2 = &Stu{}
s1.name = "jack"
s2.age = 12
fmt.Println(s1)
fmt.Println(s2)
}
//输出
//未赋值的成员都是默认值,数值是0,字符串是空串,布尔是false,指针是nil
&{jack 0 0}
&{ 12 0}
12 方法与函数
方法是一种作用于特定类型的函数,这种特定类型变量叫做接收器(receiver),即方法作用的目标
接收器分为指针型接收器,非指针型接收器,可以理解为前者是读写的,后者是只读的
//为(11)中的Stu结构体增加方法
type Stu struct {
name string
age int
score int
}
//指针型接收器
func (s *Stu) Init(name string, age int, score int) {
s.name = name
s.age = age
s.score = score
}
func main() {
var s1 = new(Stu)
var s2 = &Stu{}
s1.name = "jack"
s2.age = 12
fmt.Println(s1)
fmt.Println(s2)
s3 := new(Stu)
s3.Init("tom", 22, 100)
fmt.Println(s3)
}
//输出
&{jack 0 0}
&{ 12 0}
&{tom 22 100}
13 接口interface
① 接口只有方法定义,没有方法实现,体现的是程序的多态和低耦合,其目录是解决单一继承问题
② 当然go里没有类的概念,其实是借助struct实现相关理念的,接口表现在对象具有某种行为能力
③ 要实现一个接口,必须实现该接口里面的所有方法,是非侵入式的,php的接口实现是侵入式的(即接口与类实现了强绑定,改动接口,类必然跟着改动,但是goland不一样)
④ 无任何方法的接口为空接口,故所有类型都实现了空接口,空接口实现保存任何数据
⑤ 一个struct可以实现接口的所有方法,那么可以说struct实现了这个接口,如果只实现部分接口,则说明struct没有实现该接口
⑥ 隐示实现,Go编译器将自动在需要的时候检查两个类型之间的实现关系,故没有implement
⑦ 一个类型可以实现多个接口,一个接口也可以被多个类型实现
⑧ 接口断言实现转换(v.(T))
//定义结构体
type Num struct {
n1 int
n2 int
}
//接口是包含一组方法定义,表示具备的行为
type Calculate interface {
add() int
minute() int
multiply() int
divide() int
mod() int
}
func (num Num) add() int {
return num.n1 + num.n2
}
func (num Num) minute() int {
return num.n1 - num.n2
}
func (num Num) multiply() int {
return num.n1 * num.n2
}
func (num Num) divide() int {
return num.n1 / num.n2
}
func (num Num) mod() int {
return num.n1 % num.n2
}
num := Num {5,2}
fmt.Println(num)
fmt.Printf("add: %d\n", num.add())
fmt.Printf("minute: %d\n", num.minute())
fmt.Printf("multiply: %d\n", num.multiply())
fmt.Printf("divide: %d\n", num.divide())
fmt.Printf("mod: %d\n", num.mod())
14 访问权限
结构体成员、函数、方法等基于首字符的大小写来实现访问权限控制
大写:允许包外访问
小写:只能包内访问
15 并发与并行
并发:同一时间段多任务处理,各自占用时间片,实际上还是串行
并行:同一时刻多任务处理,需要多CPU配合
Go并发是基于goroutine实现的,编译器运行时完成调度,智能的将goroutine中的任务处理分配给每个CPU,是语言上支持的特性,多个goroutine借助通道channel实现通信
main 程序启动时,自动创建一个默认的goroutine,一个goroutine必须对应一个函数,所有goroutine在main函数结束时一起结束
channel是阻塞式收发数据(无接收,发送方阻塞,无发送,接收方阻塞),当然也可以非阻塞接收数据,即一次取出所有
func send(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
func main() {
ch := make(chan int)
go send(ch)
time.Sleep(1* time.Second)
for data := range ch {
fmt.Println(data)
}
}
并发数据访问控制
1 原子访问 atonic
2 互斥锁 sync.Mutex
3 等待组 sync.WaitGroup(推荐)