0.关键词:ok parttern ,type switch, duck typing, structural typing
0.基础:
//示例代码
package main //当前程序的包名
import "fmt" //导入其他包
const PI=3.14 //常量的定义
var name = "gopher" //全局变量的声明与赋值
type newType int //一般类型声明
type gopher struct{} //结构声明
type golang interface{} //接口的声明
func main(){ //main函数是程序入口启动点
Println("Hello world ")
}
- go通过package来组织代码。只有package名字为main的包才可以包含main函数。package声明必须在第一行。
- 对包的调用方式为:
.
3.别名调用、省略调用
//别名调用
import{
io "fmt"
}
io.Println("helloworld")
//省略调用(不建议)
import{
. ""
}
Println("helloworld")
4.可见性规则,大写包外,小写包内。
5.一次多个的写法
//常量定义
const(
PI = 3.14
const1 = 1
const2 =2
)
//全局变量的声明与赋值
var(
name = "gopher"
name1 = "1"
name2 = 2
)
//一般类型声明
type(
newType int
type1 float32
type2 string
)
6.bool型占用一个字节,int型占用32/64位,int8占用一个字节-128~127,uint8 占用一个字节0~255,字节型byte是uint8别名。
int16/uint16 占用2个字节,范围是-32768~32767 和0~65535
int32(rune)/uint32 占用4个字节,范围是-2^32/2 ~ 2^32/2-1 和0~2^32-1
int64/uint64 占用8个字节,范围是-2^64/2 ~ 2^64/2-1 和0~2^64-1
浮点型
float32/float64 占用4/8个字节,小数位精确到7或15位
complex64/complex128复数型,长度8/16字节
指针型uintptr 32位或64位
引用类型 slice、 map、 chan
其他类型 array、struct、 string
接口类型inteface
函数类型fun
7.默认值:值类型默认0,bool型默认false, string默认空字符串,引用类型默认new
8.iota初始0,每定义一个变量自增1,注意不是从第一次使用iota开始算,而是定义组中第一个定义算0
9.&&,左边表达式不成立时,不再执行右边表达式.
- a++在go里是语句而不是表达式,因此只能单独写,而不能放到表达式右边。
- if语句
if a:=1; a>0{//支持定义变量,该变量是局部变量,作用域是if内
fmt.Println(a)
}
12.go中只有for循环语句,没有while
for{//相当于while
}
for a<=3{//相当于while,自带条件表达式
}
for i:=0; i<3; i++{//经典格式
}
13.switch 可以使用任何表达式作为条件语句, 不需要break,一旦条件符合自动终止。如果希望继续执行下一case,需要使用fallthrough语句。支持一个右侧带分号的初始化表达式。
case 语句可以是常量也可以是表达式。
a := 1
switch a {//形式1
case 0:
fmt.Println("a=0")
case 1:
fmt.Println("a=1")
default:
fmt.Println("default")
}
a := 1
switch {//形式2
case a >0:
fmt.Println("a>0")
fallthrough
case a >1:
fmt.Println("a>1")
default:
fmt.Println("default")
}
14.跳转语句 goto, break, continue
三个语法都可以配合标签LABEL:使用,标签区分大小写。break和continue配合标签可以跳出多层循环,而goto不是跳出循环而是用于跳转到执行位置,注意避免死循环。
func main() {
LABLE:
for {
for i := 0; i < 10; i++ {
if i > 3 {
break LABLE
}
}
fmt.Println("doing")
}
fmt.Println("ok")
}
15.数组
var a [2]int
var b [1]int
b = a //不合法
var a [2]int
var b [2]int
b = a //合法
[2]int和[1]int属于不同类型,因此[]和int连在一起写,这俩不能直接相互赋值,而应该通过循环语句赋值。
[2]int和[2]int属于同一种类型,可以相互赋值。
数组没有赋值的时候会有默认值。
赋值:
a := [2]int{1,1}
a := [2]int{1}//相当于a :=[2]int{1,0}
a := [5]int{4:1}//4是对index4的元素赋值,相当于 a :=[5]int{0,0,0,0,1}
a := [...]int{1,2,3,4,5} //三个点,不指定数组长度,根据赋值自动计算
go里面数组是值类型,函数间传递时传的是copy而不是地址。
数组可以用==和!=来进行比较,除了值比较外,数组长度是类型的特征,也在比较范围内。
new创造出来的是指向数组的指针。p:= new([10]int), 指向数组的指针也可以用index对值进行操作。
多维数组
a := [2][3]int{
{1,1,1},
{2,2,2}
}
16.切片Slice
本身不是数组,它指向底层的数组的某元素地址,作为变长数组的替代方案,Slice本身是引用类型。
len()获取元素个数,cap()获取容量,make()创建,make([]T,len,cap),其中cap可以省略。cap会成倍自增。因为重新分配地址效率比较低,所以最好事先设置好合理的cap。
注意如果多个Slice指向同一个数组,其中一个值改变会影响全部。
a := [10]int{}
s1 := a[5:10] //5是起始索引,10这个是终止索引,包含起始索引,不包含终止索引,所以是10
s1 := a[5:len(a)]
s1 := a[5:] 取第5个到最后一个
s1 := a[:5] 取前4个,不包含第五个
var s1 []int //声明,方括号里不用...
s1 = make([]int,9) //创建
如图,Slice最大容量不能超过他所指的数组。
Reslice时索引以被Slice的切片为准,索引不可以超过被slice的切片的容量cap()值。
Append 可以在slice尾部追加元素,可以将一个slice追加在另一个slice尾部,如最终长度未超原始,则返回原始slice,如超过,则重新分配数组并拷贝原始数据,这时注意对元素的改变是否会影响其他slice。
s1 := make([]int,3,6)
s1 = append(s1,1,2,3)
copy时,不改变slice的容量,超出时抛弃。可以指定位置拷贝到指定位置
17.map key-value形式存储数据,key必须是支持==或者!=运算的类型,不能是函数、map、slice。
map查找比线性搜索快,但是比索引访问慢100倍。
map使用make创建,make([keyType]valueType,cap),cap同slice的cap,可省略,自动扩倍,尽量提供合理的cap。
使用len()获取元素个数。delete()删除某键值对。
使用for range对map和slice进行迭代操作。
var m map[int]string//声明
m = map[int]string{}
或者m = make(map[int]string)
m[1] = "ok"
delete(m,1)
//复杂map
var m map[int]map[int]string
m = make(map[int]map[int]string)
m[1] = make(map[int]string) //必须对每一层初始化
m[1][1] = "OK"
a := m[1][1]
fmt.Println(a)
//用多返回值检查map的值是否存在
a, ok := m[1][2]
if !ok {
//初始化处理
}
for range 表达式中的v是拷贝,而非引用,对v的改变并不会改变原始数据集中的值,使用时要非常注意。
- 我们可以给任何⾃定义类型添加⼀个或多个⽅法。每 种类型对应的⽅法必须和类型的定义在同⼀个包中,因此是⽆法给 int 这类内置类型添加⽅法的(因 为⽅法的定义和类型的定义不在⼀个包中)。
分割线
1.垃圾回收
「可达性分析」这条路线。Go、Java、.Net
go用的是三色标记算法。
Go的垃圾回收官方形容为 非分代 非紧缩 写屏障 并发标记清理。非分代是golang GC区别于JVM GC分代模型的特点;非紧缩意味着在回收垃圾的过程中,不需要像复制算法那样移动内存中的对象,这样避免STW过长;标记清理算法的字面解释,就是将可达的内存块进行标记mark,最后没有标记的不可达内存块将进行清理sweep;Golang中实现标记功能的算法就是三色标记法,Golang里面三色标记法会造成错标问题,使用写屏障来解决这种问题,而JVM里面的CMS和G1解决错标或者漏标问题的算法分别是Increment Update和SATB
为什么引用计数不好用呢?因为它有一个特别严重的问题:无法处理循环引用。
标记 GC ROOT 能关联到的对象。这里会 STW。
从 GCRoots 的直接关联对象开始遍历整个对象图。这里不会STW。
2.协程只有和异步IO结合起来才能发挥出最大的威力。https://zhuanlan.zhihu.com/p/172471249
(线程每个占4M内存)
3.GMP https://www.cnblogs.com/lvpengbo/p/13973906.html
P的个数由GOMAXPROCS指定,是固定的,因此限制最大并发数
M的个数是不定的,由Go Runtime调整,默认最大限制为10000个。(所以Go最大占39G内存?)
4.数组使用时的注意点:https://blog.csdn.net/vrg000/article/details/82225706
数组的赋值代价是非常大的,相当于把一块内存完全拷贝。
切片使用时的注意点:
切片结构中使用了指针,存在深拷贝问题。
- Go有goroutines而不是线程。 它们从堆中消耗了大约2KB的内存。 因此,您可以随时旋转数百万个goroutine。
在Java中创建新线程不是内存有效的。 由于每个线程消耗大约1MB的内存堆大小,并且最终如果你开始旋转数千个线程
6.go一切皆类型,包括函数
7.()的意思是调用某个函数
8.理解函数闭包、defer
func main() {
fmt.Println("hello worlds!")
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()
}
}
9.go没有class,没有继承
10.struct 值传递
func main() {
a := &person{//加取地址符号,go语言中使用的时候不用加*号
Name: "haha",
Age: 20,
}
fmt.Println(a)
A(a)
fmt.Println(a)
}
type person struct {
Name string
Age int
}
func A(per *person) {
per.Age = 13
fmt.Println("A",per)
}
11.匿名结构体
func main() {
a:= struct {
Name string
Age int
}{
Name: "will",
Age: 20,
}
fmt.Println(a)
}
func main() {
a:= person{Name: "will",Age: 20}
a.Contact.Phone = "12312313"
a.Contact.City = "beijing"
fmt.Println(a)
}
type person struct {
Name string
Age int
Contact struct{
Phone ,City string
}
}
func main() {
a:= struct {
string
int
}{
"iii",
20,
}
fmt.Println(a)
}
func main() {
a := teacher{Name: "will", Age: 20, human: human{Sex: 0}}
a.Name = "will1"
a.Age = 19
a.Sex = 0
a.human.Sex = 11
fmt.Println(a)
}
type human struct {
Sex int
}
type teacher struct {
human
Name string
Age int
}
type student struct {
human
Name string
Age int
}
- 字段大写是公有字段,小写开头是私有字段只能在包内访问
12.method的receiver可以是struct也可以int等任何类型,但是需要注意的是必须是同一个包里的类型才可以method绑定receiver,比如int是系统包里的,在自己的包里就不能直接绑定,而是要type aaa int定义一下,绑定aaa
13.method
type person struct {
name string
}
func main() {
a := person{
name: "will",
}
a.print11()
fmt.Println(a)
}
func (p person ) print11() {
fmt.Println("666")
}
type TZ int
func main() {
var a TZ = 11
a.print11()
fmt.Println(a)
}
func (p TZ) print11() {
fmt.Println("6666")
}
14.method value和method expression
func main() {
var a TZ = 11
a.print11()// method value
(*TZ).print11(&a)//method expression
fmt.Println(a)
}
15.接口 是一个或者多个方法签名的集合,若某类型实现该接口的所有方法签名,即算实现该接口,无需声明实现了哪个接口,这称为structural typing。
接口没有字段。
接口可以嵌入接口。
因为go不用声明实现了哪个接口,只要实现其方法就算,所以go中所有类型都实现了empty接口(aaa interface{})即空接口是任何类型的容器。interface{}是空接口类型。
type switch。
接口转换。
给接口类型赋值是复制,无法修改原来的内容。
只有当接口存储的类型和对象都为nil时,接口才等于nil。
接口调用不会做receiver的自动转换。
16.用goland可以方便的一次性把一个类型需要的接口的method全部实现。
17.接口示例,空接口示例
type USB interface {
Name() string
Connector
}
type Connector interface {
connect()
}
type PhoneConnector struct {
name string
}
func (p PhoneConnector) Name() string {
return p.name
}
func (p PhoneConnector) connect() {
fmt.Println("connected:", p.name)
}
func main() {
a := PhoneConnector{"PhoneConnector1"}
a.connect()
Disconnect(a)
}
func Disconnect(usb interface{}) {
switch v := usb.(type) {
case PhoneConnector:
fmt.Println("disconnected", v.name)
default:
fmt.Println("unknown device")
}
}
18.反射reflection
typeOf 和valueOf,需要是reflect.Struct类型的才行
type User struct {
Id int
Name string
Age int
}
func (u User) Hello() {
fmt.Println("hello")
}
func main() {
u := User{1, "will", 20}
Info(u)
}
func Info(o interface{}) {
t := reflect.TypeOf(o)
fmt.Println("type:", t)
v := reflect.ValueOf(o)
fmt.Println("value:")
if k := t.Kind(); k != reflect.Struct {
fmt.Println("not reflect struct")
return
}
for i := 0; i < t.NumField(); i++ {//打印字段
f := t.Field(i)
val := v.Field(i).Interface()
fmt.Printf("%6s: %v = %v\n\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: main.User
value:
Id: int = 1
Name: string = will
Age: int = 20
Hello: func(main.User)
*/
19.嵌入字段和匿名字段的反射
type User struct {
Id int
Name string
Age int
}
type Manager struct {
User
title string
}
func main() {
m := Manager{User{1,"will",20},"title1"}
t := reflect.TypeOf(m)
fmt.Printf("%#v\n",t.FieldByIndex([]int{0,1}))//打印嵌入字段的第2个字段
}
20.ptr interface 0x88888这样的地址 后.Elem()可以代指地址对应的元素
21.用反射修改内容
func main() {
x := 123
v := reflect.ValueOf(&x)
v.Elem().SetInt(666)
fmt.Println(v.Elem())
}
22.反射调用方法
type User struct {
Id int
Name string
Age int
}
func (u User) Hello(name string) {
fmt.Println("hello", name, ",my name is", u.Name)
}
func main() {
u := User{1, "will", 20}
v := reflect.ValueOf(u)
mv := v.MethodByName("Hello")
args := []reflect.Value{reflect.ValueOf("haha")}
mv.Call(args)
}
23.利用反射修改值
type User struct {
Id int
Name string
Age int
}
func (u User) Hello(name string) {
fmt.Println("hello", name, ",my name is", u.Name)
}
func main() {
u := User{1, "will", 20}
Set(&u)
fmt.Println(u)
}
func Set(o interface{}) {
v := reflect.ValueOf(o)
if v.Kind() == reflect.Ptr && !v.Elem().CanSet() {
fmt.Println("error info")
return
} else {
v = v.Elem()
}
f := v.FieldByName("Name")
if !f.IsValid() {
fmt.Println("BAD")
return
}
if f.Kind() == reflect.String {
f.SetString("ByeBye")
}
}
24.并发
concurrency-is-not-parallelism
https://www.cyningsun.com/12-09-2019/concurrency-is-not-parallelism.html
并发不是并行,并发比并行更加优秀。
goroutine通过通信来共享内存,而不是共享内存来通信
25.一个最简单的goroutine
func main() {
go GO()
time.Sleep(1 * time.Second)
}
func GO() {
fmt.Printf("go!!!")
}
func main() {
c :=make(chan bool)
go func() {
fmt.Println("go!!!")
c<- true
close(c)//不close的话会死锁
}()
for v:=range c{
fmt.Println(v)
}
}
runtime.GOMAXPROCS(runtime.NumCPU())//利用所有cpu内核
26.通道channel有单向通道双向通道、只读、只写,阻塞、非阻塞等,还可以设置缓存。
有缓存时是同步阻塞的,无缓存是异步非阻塞的
27.用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(group *sync.WaitGroup, index int) {
a:=1
for i := 0; i < 10000000; i++ {
a+=i
}
fmt.Println(index,a)
group.Done()
}
28.select用来处理 多个channel事务(包括接收和发送),随机处理无顺序。
可以用空select{}阻塞main。
可以设置超时时间:
29.练习goroutaine channel
var c chan string
func Pingpang() {
i:=0
for {
fmt.Println("in pingpang",<-c)
c<-fmt.Sprintln("from pingpang :hi, #%",i)
i++
}
}
func main() {
c = make(chan string)
go Pingpang()
for i := 0; i < 5; i++ {
c<-fmt.Sprintln("from main :hi,#% ",i)
fmt.Println("in main",<-c)
}
}
//结果
in pingpang from main :hi,#% 0
in main from pingpang :hi, #% 0
in pingpang from main :hi,#% 1
in main from pingpang :hi, #% 1
in pingpang from main :hi,#% 2
in main from pingpang :hi, #% 2
in pingpang from main :hi,#% 3
in main from pingpang :hi, #% 3
in pingpang from main :hi,#% 4
in main from pingpang :hi, #% 4
30.go的一些坑
slice自动增加容量时会重新移动到新的内存地址,导致原来的引用无效。
//错误做法
func Pingpang(s []int) {
s = append(s, 3)
}
func main() {
s := make([]int, 0)
fmt.Println(s)
Pingpang(s)
fmt.Println(s)
}
//正确做法
func Pingpang(s []int) []int{
s = append(s, 3)
return s
}
func main() {
s := make([]int, 0)
fmt.Println(s)
s = Pingpang(s)
fmt.Println(s)
}
闭包做的变量引用的坑
//错误做法
func main() {
s := []string{"a", "b", "c"}
for _, s2 := range s {
go func() {
fmt.Println(s2)
}()
}
select {
}
}
打印全是c
//正确做法是作为参数传递进去
func main() {
s := []string{"a", "b", "c"}
for _, s2 := range s {
go func(s2 string) {
fmt.Println(s2)
}(s2)
}
select {
}
}
//输出
c
b
a