go笔记

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 ")
}
  1. go通过package来组织代码。只有package名字为main的包才可以包含main函数。package声明必须在第一行。
  2. 对包的调用方式为:.

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.&&,左边表达式不成立时,不再执行右边表达式.

  1. a++在go里是语句而不是表达式,因此只能单独写,而不能放到表达式右边。
  2. 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) //创建
截屏2021-10-14 下午2.40.17.png

如图,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的改变并不会改变原始数据集中的值,使用时要非常注意。

  1. 我们可以给任何⾃定义类型添加⼀个或多个⽅法。每 种类型对应的⽅法必须和类型的定义在同⼀个包中,因此是⽆法给 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
数组的赋值代价是非常大的,相当于把一块内存完全拷贝。
切片使用时的注意点:
切片结构中使用了指针,存在深拷贝问题。

  1. 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
}
  1. 字段大写是公有字段,小写开头是私有字段只能在包内访问

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

你可能感兴趣的:(go笔记)