golang问题

4,665 阅读32分钟

【切片】
1.内部实现
数据结构中有三个字段

地址指针指向底层数组
长度切片能够访问的元素个数
容量切片容许增长到的元素个数

所以切片在函数间传递成本非常低,指针4个字节,长度和容量是整型各4个字节,一共12个字节,无论切片地址指针指向的底层数组有多大都不影响,但是在函数中对切片底层数组修改外部会有感知

2.初始化

初始化方法说明
slice := make([]int,5)创建一个整型切片,长度容量都是5,底层数组为00000
slice := make([]int,3,5)创建整型切片,长度为3,容量为5,底层数组为000,后两个元素不能访问
slice := []int{1,2,3}切片字面量声明切片,长度容量都是3
slice := []int{2:1}索引声明切片,第3个元素初始为1,长度容量都是3,底层001
var slice []int创建nil切片,表示一个不存在的切片,地址指针为nil,
函数要求返回一个切片但是发生异常时可以使用
slice := make([]int,0)

slice := []int{}
声明空切片,底层数组包含0个元素,没有分配任何存储空间,
可以用来表示空集合

3.使用切片
从一个切片中创建的新切片,两者共享同一个底层数组,如果修改共享部分,另一个切片有感知

slice := []int{1,2,3}
newSlice := slice[1:2]

4.切片作为参数传递 image.png 1.把切片作为一个参数时,传进去的是复制的切片结构体,所以初始和进入接口的 切片结构体地址不一样,但是结构体里面的三个元素是复制过去的,所以数组地址和容量,长度都是一样的
2.添加元素,导致切片扩展,数组重新划分了新地址存储数值,所以append操作后数组地址和进入接口后数组地址不一样了,但是切片结构体地址不会因为append操作变化,append只会影响数组的地址
3.调用接口不会影响外部的切片结构体的信息,接口内部操作只会改变复制的结构体里面的数组信息,容量和长度
4.接口内部如果改变了数组某个位置的数值,会影响外部切片,具体看操作的时候数组地址是否和外部一致,图中在共享一个地址的时候修改i[0]=9,所以外部也会受影响,如果在append之后操作,在外部数组不会受影响,因为不共享数组了

变量底层数组长度容量
slice1 2 333
newSlice2 312

newSlice从slice索引1开始创建,只能共享从这个索引开始的数组部分,只复制了slice的一个元素,所以长度是1,但是slice从索引1开始后面有两个元素,所以容量是2 所以newSlice[0]和slice[1]实际是同一个内存地址上的值,修改一个切片另一个也会被改

newSlice[0]=-1 //slice[1]也变成-1
newSlice=append(newSlice,-2) //在执行之前 newSlice -1,长度1 容量2,
                                        slice 1 -1 3 长度3 容量3
                                执行之后,newSlice -1 -2 长度容量都是2
                                把slice 第三个元素改了 1 -1 -2

append函数能够将容量合并到切片长度里,没有在长度里的容量是不能访问的,slice[i],i所在位置不再长度范围内运行会出错

slice := make([]int,1,3) //0 _ _
print(slice[1])  //会出错,长度为1 只能访问slice[0]

append函数可以实现切片的动态增长,当容量长度一致,append再往切片中添加新元素时,会重新分配一块更大的内存给切片底层数组,这个时候底层数组地址是改变的

所以当两个切片共享同一底层数组时,其中一个容量长度相等,再添加新元素,两个切片就会分离

slice := []int{1,2,3}
newSlice := slice[1,2]
slice := append(slice,4)

slice append之后会和newSlice分离,newSlice底层数组不变,slice底层数组迁移到一个新地址,并且长度为4,容量为6

5.数组与切片

package main

import "fmt"

func main() {
    var p [100]int
    var m interface{} = [...]int{99: 0}
    fmt.Println(p == m)
}

返回true
解析:首先 [...]int{99: 0} 是数组还是切片 ?
切片和数据的主要区别是:切片容量可变,而数组长度不可变。所以数组在声明时是需要确定其长度的。切片不需要。在实际开发时我们区分中括号里是否有设置长度就可以区分二者了,如果设置了长度就一定是数组,否则一定是切片。

举例说明:

var a = [0]int{} 长度为0的数组

var c = [6]int{} 长度为6的数组

var b = [...]int{1,3,7} 长度为3的数组 其中的三个点是表示数组长度以声明时实际赋予的元素个数为依据

所以说 [...]int{99: 0} 是数组
然后接口和非接口类型可以比较判断吗?
1.interface类型变量与非interface类型变量判等时,首先要求非interface类型实现了接口,否则编译不通过(本题接口方法集为空,我们认为所有类型都实现了该接口)
2.满足上一条的前提下,interface类型变量的动态类型、值均与非interface类型变量相同时,两个变量判等结果为true,因为[...]int{99: 0} 是数组 而且长度是100,值都为0,所以答案为true

6.数组比较

package main

import "fmt"

func main() {
    type pos [2]int
    a := pos{4, 5}
    b := pos{4, 5}
    fmt.Println(a == b)
}

返回 true
数组是值类型,长度相等,每个元素也相等,数组就相等

7.append 参数类型

func main() {
   var nums1 []interface{}
   nums2 := []int{1, 3, 4}
   nums3 := append(nums1, nums2...) //报错 cannot use nums2 (variable of type []int) as type []interface{} in argument to append
   //nums3 := append(nums1, nums2[0],nums2[1],nums2[2]) //ok  int实现了interface{} nums3长度为3
   //nums3 := append(nums1, nums2) // ok nums2作为一个元素 append到nums1上  []int实现了interface{} nums3长度为1
   fmt.Println(len(nums3))
}

【map】
1.内部实现
散列表,映射是一个存储键值对的无序集合
2.初始化

初始化说明
mp := map[int]int{}创建空映射,可以直接mp[1]=1
var mp map[int]int声明映射创建了一个nil映射,不能直接通过mp[1]=2添加元素
mp := make(map[int]int)创建一个映射
mp := map[int]int{1:1,2:2}创建映射,用两个键值对初始化映射

3.判断键是否存在

value,exist := mp[1]
if exist{
    print(value)
}

4.如果获取一个不存在的key的值,不会出错,返回类型默认值

	msg := make(map[int64]int64)
	msg[1]=1
	msg[2]=2
	fmt.Println(msg[1],msg[2],msg[3])//return 1 2 0

5.nil map 和 空map的关系
nil map和empty map的关系,就像nil slice和empty slice一样,两者都是空对象,未存储任何数据,但前者不指向底层数据结构,后者指向底层数据结构,只不过指向的底层对象是空对象
直接读取nil map:m[“a”] 并不会报错,会返回默认类型的空值
【defer用法】
1.defer执行顺序先进后出,panic语句在defer语句后面执行

package main

import (
    "fmt"
)

func main() {
    defer_call()
}

func defer_call() {
    defer func() { fmt.Println("打印前") }()
    defer func() { fmt.Println("打印中") }()
    defer func() { fmt.Println("打印后") }()

    panic("触发异常")
}

结果为

打印后
打印中
打印前
panic: 触发异常

解释
defer语句执行顺序是先进后出,类似于入栈,“打印前”先入栈所以最后一个打印,panic语句会导致函数退出执行,但函数退出执行前要先执行defer语句,所以先打印defer 中的内容,再panic

panic发生的时候,协程内是否会把panic信息在栈中层层传递,这个文章讲的很好 blog.csdn.net/pengpengzho…

1.每一个协程会维护自己的延迟函数链表
(2.panic对应函数底层逻辑,先执行协程内延迟函数链表中延迟函数,然后打印panic信息,然后执行exit结束整个进程,所以panic信息不会传递给父方法,其他协程因为进程终止也结束,并且不会调用延迟函数
(3.defer是在执行到的时候会放入协程的延迟函数链表,如果panic发生了,或者return发生了,后面的defer加不到链表中,不会被执行
(4.recover 的时候panic会被新的panic信息终止,返回最新的panic

注意上面说的是在一个协程里面,不是在一个方法里面,如果是在一个协程里面,多个递归调用方法panic也是可以往上传递的

func a() {
   panic("panic")
}
func b() {
   a()
}
func c() {
   defer func() {
      e := recover()
      if e != nil {
         fmt.Println("c", e)
      }
   }()
   b()
}
func main() {
   c()
   time.Sleep(10 * time.Second)
}

返回的是 c panic,因为3个方法是在同一个协程里面

如果func b是这样

func b() {
   go a()
}

那就会捕捉不到panic直接崩了,因为b新开了一个协程执行a,不再同一个协程里面了,c也就捕捉不到直接panic了 但是如果function a是这样

func a() {
   defer func() {
      e := recover()
      if e != nil {
         fmt.Println("a", e)
      }
   }()
   panic("panic")
}

返回是a panic,因为a自己在方法里面捕捉了

defer里面如果发生了panic,如果后面还没有执行的defer有recover可以捕捉到这个panic也不会崩,比如下面这样

func a() {
   defer func() {
      panic("a panic")
   }()
}
func b() {
   a()
}
func c() {
   defer func() {
      e := recover()
      if e != nil {
         fmt.Println("c", e)
      }
   }()
   b()
}
func main() {
   c()
   time.Sleep(10 * time.Second)

}

在a里面的panic可以被前面入栈的defer recover捕捉到,返回是c a panic

但是如果recover是在后面的子方法a里面,前面的方法c的panic是捕捉不到的

func a() {
   defer func() {
      e := recover()
      if e != nil {
         fmt.Println("a", e)
      } else {
         fmt.Println("a no panic")
      }
   }()
}
func b() {
   a()
}
func c() {
   b()
   panic("c panic")

}
func main() {
   c()
}

result

image.png 因为每一个方法在结束前会调用自己的defer方法,a在调用的时候c还没有panic所以会打印 a no panic,a返回之后c继续往下执行panic没有recover方法所以崩

2.defer语句内部参数为函数调用

package main

func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}

func main() {
    a := 1
    b := 2
    defer calc("1", a, calc("10", a, b))
    a = 0
    defer calc("2", a, calc("20", a, b))
    b = 1
}

结果

10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4

解析
运行到第一个defer语句时,要对其参数“入栈”,遇到参数还需要计算先计算出来再“入栈”(跟变量类型有关?静态,运行时之类的),所以先计算calc("10", a, b),遇到第二个defer语句在计算其中的calc("20", a, b),函数返回时在执行两个defer语句
3.先执行return后面的语句再执行defer语句

package main

func main() {

	println(DeferFunc1(1))
	println(DeferFunc2(1))
	println(DeferFunc3(1))
	println(DeferFunc4(1))
}
func plus1(i int )int{
	i++
return i
}
func DeferFunc1(i int) (t int) {
	t = i
	defer func() {
		t += 3
	}()
	return t
}

func DeferFunc2(i int) int {
	t := i
	defer func() {
		t += 3
	}()
	return t
}

func DeferFunc3(i int) (t int) {
	defer func() {
		t += i
	}()
	return 2
}
func DeferFunc4(i int) (t int) {
	t=i
	defer func() {
		t += i
	}()
	return plus1(t)
}

结果

4
1
3
3

解析
DeferFunc1声明了一个变量t作为返回值,想象现在有一块内存里面就是t的值,目前初始为0,
t = i//第一步t被赋值,t所在的内存上被写上值1
defer func() {
t += 3//第三步t被再次赋值,内存上的值被改写为4,然后返回,返回值是内存上的值即4
}()
return t//第二步return,发现有defer语句,继而去执行defer语句
DeferFunc2函数中没有给返回值声明一个显示变量,当执行return t时,将t的值复制到了返回值的内存中,注意这里t的地址和返回值的地址不一样,只是将t的值复制给了返回值地址,所以defer中对t操作只会改变t所在内存处的值,对返回值没影响
DeferFunc3 中return 2 将2复制给了返回值地址,也就是t所在的地址,此时t=2,defer语句中对t操作,就是对返回值地址里的值操作,所以返回3
DeferFunc4中会先执行plus1函数,t变成2,再执行defer函数

return 底层做了什么很重要,要搞清楚,才能理解上面的题目

golang语言中的return语句不是原子操作,分为(1)返回值赋值和(2)RET指令两步。而defer语句执行在赋值之后,RET之前

返回参数的地址也很重要比如DeferFunc2,返回没有参数名,函数会隐式的分配一块内存给返回值,这个地址跟t不同,所以 return t中 ,第一步返回值赋值,是把t的值复制给了隐式分配的那个地址,defer后续对t的操作都跟他无关,所以最后输出值是1 这个时候t是等于3的;DeferFunc3 返回值显式指明是t,return 2 第一步就是给t赋值为2,执行defer t加1 等于3 ,最后返回 3;DeferFunc4 第一步返回值赋值,plus方法执行之后t=2 赋值给t,defer 里面+1,等于3最后返回

func e1() {
   var err error
   defer fmt.Println(err)
   err = errors.New("defer err1")
}
func e2() {
   var err error
   defer func() {
      fmt.Println(err)
   }()
   err = errors.New("defer err2")
}
func e3() {
   var err error
   defer func(err error) {
      fmt.Println(err)

   }(err)
   err = errors.New("defer err3")
}
func main() {
   e1()
   e2()
   e3()
}

返回

<nil>
defer err2
<nil>

解析:如果明确传参的,应该是复制 参数的值到方法中去,把参数放到方法的栈上了,后续这个参数在外面的修改就不会影响到方法内部,如果没有传参,闭包里面会记录变量的地址,地址上的值发生变化,内部有感知

4.只有最后一个panic会被recover捕获

package main

import (
	"fmt"
)

func main()  {
	defer func() {
		if err:=recover();err!=nil{
			fmt.Println(err)
		}else {
			fmt.Println("fatal")
		}
	}()

	defer func() {
		panic("defer panic")
	}()
	panic("panic")
}

结果

defer panic

解析
执行第一个panic("panic")后,执行defer语句,里面又是一个panic,然后执行上面的defer,里面的recover语句只会捕获最后一个panic,也就是defer panic,所以打印defer panic,另外程序也不会崩,因为panic被recover捕获了,只会打印出错误信息 也就是如果有多个panic,后面的panic会覆盖前面的,最后只会保留最新的panic信息

【foreach】
1.foreach创建副本

package main

func main()  {
	pase_student()
}

type student struct {
	Name string
	Age  int
}

func pase_student() {
	m := make(map[string]*student)
	stus := []student{
		{Name: "zhou", Age: 24},
		{Name: "li", Age: 23},
		{Name: "wang", Age: 22},
	}
	// 错误写法
	for i, stu := range stus {
		println(&stu)
		println(&stus[i])
		m[stu.Name] = &stu
	}

	for k,v:=range m{
		println(k,"=>",v.Name,v.Age)
	}

	// 正确
	for i:=0;i<len(stus);i++  {
		m[stus[i].Name] = &stus[i]
	}
	for k,v:=range m{
		println(k,"=>",v.Name,v.Age)
	}
}

结果

0xc000004060
0xc000048000
0xc000004060
0xc000048018
0xc000004060
0xc000048030
zhou => wang 22
li => wang 22
wang => wang 22
zhou => zhou 24
li => li 23
wang => wang 22

解析 foreach中创建变量stu(有自己的内存地址,不同于stus地址)会复制stus的元素值到自己所在位置,可以看到结果中stu的地址和对应的stus[i]地址并不相同,最后map中键值是正确的,但是值(是个指针)指向的并不是真正的存储学生信息的地址,然后foreach创建的一个temp变量的地址,也就是stu的地址,这个地址里面存储着foreach最后一次执行复制的stus里面的内容,所以打印出来m所有键的value是同一个值 正确就是直接按下标找到真正地址复制给map

【go闭包,执行随机性】
1.并发函数内部变量

package main

import (
	"runtime"
	"sync"
	"fmt"
)

func main() {
runtime.GOMAXPROCS(1)
wg := sync.WaitGroup{}
wg.Add(20)
for i := 0; i < 10; i++ {
go func() {
fmt.Println("A: ", i)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println("B: ", i)
wg.Done()
}(i)
}
wg.Wait()
}

结果

B:  9
A:  10
A:  10
A:  10
A:  10
A:  10
A:  10
A:  10
A:  10
A:  10
A:  10
B:  0
B:  1
B:  2
B:  3
B:  4
B:  5
B:  6
B:  7
B:  8

解析
第一个循环中并发函数内部的变量i是外部循环变量,循环很快就会遍历完,所以等并发函数要执行的时候,i已经到了循环终止时的10,所以打印的都是10 第二个循环中,i是入参变量,对当前的外部循环变量i值拷贝,所以即使并发函数执行的时候外部i已经是10,但是并发函数内部i是当时复制下来的数,并发函数之间执行顺序不确定,所以打印的0-9数顺序也不确定

附加

image.png 答案

ans1不一定是 15, ans2 不一定是 15.
ans1因为闭包问题,ans2因为并发加操作不是原子性的

【go组合无继承】

package main

import "fmt"

type People struct{}

func (p *People) ShowA() {
fmt.Println("people showA")
p.ShowB()
}
func (p *People) ShowB() {
fmt.Println("people showB")
}

type Teacher struct {
People
}

func (t *Teacher) ShowB() {
fmt.Println("teacher showB")
}

//func (t *Teacher) ShowA() {
//	fmt.Println("teacher showA")
//}
func main() {
t := Teacher{}
t.ShowA()
}

结果

people showA
people showB

解析 go组合模式 被组合的类型People所包含的方法虽然升级成了外部类型Teacher这个组合类型的方法(一定要是匿名字段),但它们的方法(ShowA())调用时接受者并没有发生变化。 但是如果将注释代码去掉注释结果就变成了

teacher showA

因为函数相同时,外部类型的方法会隐藏内部类型的方法
【select语句】
1.select随机性 会触发异常吗?

func main() {
    runtime.GOMAXPROCS(1)
    int_chan := make(chan int, 1)
    string_chan := make(chan string, 1)
    int_chan <- 1
    string_chan <- "hello"
    select {
    case value := <-int_chan:
        fmt.Println(value)
    case value := <-string_chan:
        panic(value)
    }
}

结果 不确定 select可以在多个chan间等待执行。有三点原则: select 中只要有一个case能return,则立刻执行 当如果同一时间有多个case均能return则伪随机方式抽取任意一个执行。 如果没有一个case能return则可以执行”default”块
【make用法】
1.make初始化值

func main() {
    s := make([]int, 5)
    s = append(s, 1, 2, 3)
    fmt.Println(s)
}

结果

0 0 0 0 0 1 2 3

解析 make会根据创建元素的类型将元素初始化为零值,这里创建5个int类型的值,所以初始化5个0,后面追加1,2,3, make 类型是切片时还需要第二个参数,类型个数,甚至还可以有第三个 类型是阻塞通道或者map则不需要 make(chan int) make(map[string]int) make([]int,10) 一个int数组,里面10个元素值为0
make([]int,0,10) 一个数组,参数2是len目前元素个数,参数3是cap数组容量,所以元素个数现在为0 ,但能装10个
make([]int,5,10) 已经有5个元素值为0,能装10个
【map】
1.map线程不安全

package main

import (
	"sync"
	)

type UserAges struct {
ages map[string]int
sync.Mutex
}

func (ua *UserAges) Add(name string, age int) {
ua.Lock()
defer ua.Unlock()
ua.ages[name] = age
}

func (ua *UserAges) Get(name string) int {
if age, ok := ua.ages[name]; ok {
return age
}
return -1
}
func main(){
	ua := new (UserAges)
	ua.ages = make(map[string]int)
	go func() {
		for{
			ua.Add("jack",22)
		}
	}()

	go func() {
		for{
			ua.Get("jack")
		}
	}()
	c := make(chan int)
	c<-1
}

结果

fatal error: concurrent map read and map write

解析 写的时候虽然加了锁,但是在写的时候可能会同时读,map在并发情况下时线程不安全的,要在多线程中使用map,应该用sync.Map,修改程序,对比两个程序,观察sync.Map用法,他不需要初始化,有load store 两个重要用法

package main

import (
	"sync"
	)

type UserAges struct {
ages sync.Map
}

func (ua *UserAges) Add(name string, age int) {
	ua.ages.Store(name,age)
	println("add")
}

func (ua *UserAges) Get(name string) int {
	println("get")
	if age, ok := ua.ages.Load(name); ok {
	return age.(int)
	}
	println("get")
	return -1
}
func main(){
	ua := new (UserAges)
	go func() {
		for{
			ua.Add("jack",22)
		}
	}()

	go func() {
		for{
			ua.Get("jack")
		}
	}()
	c := make(chan int)
	c<-1
}

【channel】
1.无缓冲channel

package main

import "sync"

type threadSafeSet struct{
	sync.RWMutex
	s []int
}
func (set *threadSafeSet) Iter() <-chan interface{} {
	wg := sync.WaitGroup{}
	wg.Add(1)
	ch := make(chan interface{})
	//ch := make(chan interface{},len(set.s))
	go func() {
		set.RLock()

		for elem := range set.s {
			ch <- elem
		}

		wg.Done()
		close(ch)
		set.RUnlock()

	}()
	wg.Wait()
	return ch
}
func main(){
	set := new (threadSafeSet)
	set.s = make([]int,5)
	set.Iter()
}

结果

fatal error: all goroutines are asleep - deadlock!

解析
ch是无缓冲的通道,第一个元素放进通道的时候就会阻塞,除非从通道中读数据,否则会一直阻塞,所以会报错,如果用注释的那一行,则不会报错,正好通道装满返回
2.从一个nil的通道读数据会怎样?
阻塞

【go鸭子类型】
1.鸭子类型

package main

import (
	"fmt"
)

type People interface {
	Speak(string) string
}

type Stduent struct{}

func (stu *Stduent) Speak(think string) (talk string) {
	if think == "bitch" {
		talk = "You are a good boy"
	} else {
		talk = "hi"
	}
	return
}
//func (stu Stduent) Speak(think string) (talk string) {
//	if think == "bitch" {
//		talk = "You are a good boy"
//	} else {
//		talk = "hi"
//	}
//	return
//}
func main() {
    //var peo People = &Stduent{}
	var peo People = Stduent{}
	think := "bitch"
	fmt.Println(peo.Speak(think))
}

结果 编译不通过,因为student类没有实现people下的方法,是*student 指针类型实现的,所以换成注释的那一行就可以编译通过了,或者让student类型实现speak函数,如上面注释额函数
解析 golang中没有class的概念,而是通过interface类型转换支持动态类型语言中常见的鸭子类型,来达到运行时多态的效果,注意一定是people 是interface如果换成struct就不是这么回事了
思考:如果speak实现换成注释的函数,main函数中会编译通过吗
解析
不管是student还是&student都会通过,因为如果是&student,函数会在执行的时候加上*解地址符

接收者传值可行
指针不可行
指针可行

【interface】
1.interface不代表nil

package main

import (
	"fmt"
)

type People interface {
	Show()
}

type Student struct{}

func (stu *Student) Show() {

}

func live() People {
	var stu *Student
	return stu
}

func main() {
	if live() == nil {
		fmt.Println("AAAAAAA")
	} else {
		fmt.Println("BBBBBBB")
	}
}

结果

BBBBBBB

解析
live()函数返回的是interface类型数据,go中的接口分为两种,一种是空的接口
var in interface{} 另一种如题目:
type People interface { Show() }
他们的底层结构如下

type eface struct {      //空接口
    _type *_type         //类型信息
    data  unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type iface struct {      //带有方法的接口
    tab  *itab           //存储type信息还有结构实现方法的集合
    data unsafe.Pointer  //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type _type struct {
    size       uintptr  //类型大小
    ptrdata    uintptr  //前缀持有所有指针的内存大小
    hash       uint32   //数据hash值
    tflag      tflag
    align      uint8    //对齐
    fieldalign uint8    //嵌入结构体时的对齐
    kind       uint8    //kind 有些枚举值kind等于0是无效的
    alg        *typeAlg //函数指针数组,类型实现的所有方法
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}
type itab struct {
    inter  *interfacetype  //接口类型
    _type  *_type          //结构类型
    link   *itab
    bad    int32
    inhash int32
    fun    [1]uintptr      //可变大小 方法集合
}

可以看出iface比eface 中间多了一层itab结构。 itab 存储_type信息和[]fun方法集,从上面的结构我们就可得出,因为data指向了nil 并不代表interface 是nil
2.type只能在interface类型上使用

package main
func main() {
i := GetValue()

switch i.(type) {
case int:
println("int")
case string:
println("string")
case interface{}:
println("interface")
default:
println("unknown")
}

}

func GetValue() int {
return 1
}

结果 编译不通过,报错

cannot type switch on non-interface value i (type int)

3.interface内部结构

package main

import (
	"fmt"
)

func Foo(x interface{}) {
	if x == nil {
		fmt.Println("empty interface")
		return
	}
	fmt.Println("non-empty interface")
}
func main() {
	var x *int = nil
	Foo(x)
}

结果

non-empty interface

解析 理解的不是很透彻,还是跟interface内部结构有关,内部data可能为nil但是这个interface是不为空的
【函数】
1.返回值要么都不命名,要么都命名

package main
func main() {
	funcMui(1,2)
}


func funcMui(x,y int)(sum int,error){
	return x+y,nil
}

结果 编译不通过报错

syntax error: mixed named and unnamed function parameters

解析 在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名 如果返回值有有多个返回值必须加上括号 如果只有一个返回值并且有命名也需要加上括号
2.闭包延迟求值

package main

func test() []func()  {
	var funs []func()
	for i:=0;i<2 ;i++  {
		funs = append(funs, func() {
			println(&i,i)
		})
	}
	return funs
}

func main(){
	funs:=test()
	for _,f:=range funs{
		f()
	}
}

结果

0xc00000a048 2
0xc00000a048 2 

解析 类似于foreach中的复制,这里的i始终在同一块内存上最后打印的时候是同一个地址,且上面的值是最后跳出循环的2

func Increase() func() int {
	n := 0
	return func() int {
		n++
		return n
	}
}

func main() {
	in := Increase()
	fmt.Println(in()) // 1
	fmt.Println(in()) // 2
}

解析Increase() 返回值是一个闭包函数,该闭包函数访问了外部变量 n,那变量 n 将会一直存在,直到 in 被销毁。很显然,变量 n 占用的内存不能随着函数 Increase() 的退出而回收,因此将会逃逸到堆上

3.闭包引用相同变量

package main

func test(x int) (func(),func())  {
    return func() {
        println(x)
        x+=10
    }, func() {
        println(x)
    }
}

func main()  {
    a,b:=test(100)
    a()
    b()
}

结果

100
110

解析 x作为一个方法内的参数,地址被存进了栈内,两个返回方法里面x指向的是同一个地址,执行a的时候,把这个地址上的值+10,变成110,b方法执行的时候,打印这个值就是110了
【append】
1.append第一个参数是切片类型

package main

import "fmt"

func main() {
	list := new([]int)
	list = append(list, 1)
	fmt.Println(list)
}

结果 编译不通过

first argument to append must be slice; have *[]int

解析 new返回的是指针类型,append参数1是切片类型
2.append第二个参数

package main

import "fmt"

func main() {
	s1 := []int{1, 2, 3}
	s2 := []int{4, 5}
	s1 = append(s1, s2)
	fmt.Println(s1)
}

结果
编译不通过

cannot use s2 (type []int) as type int in append

解析 append函数形式

func append(slice []Type, elems ...Type) []Type

可以一个元素一个元素的给 slice = append(slice, elem1, elem2) 也可以切片后面加... slice = append(slice, anotherSlice...)

【结构体】
1.结构体比较

package main

import "fmt"

func main() {

	sn1 := struct {
		age  int
		name string
	}{age: 11, name: "qq"}
	sn2 := struct {
		age  int
		name string
	}{age: 11, name: "qq"}

	if sn1 == sn2 {
		fmt.Println("sn1 == sn2")
	}

	sm1 := struct {
		age int
		m   map[string]string
	}{age: 11, m: map[string]string{"a": "1"}}
	sm2 := struct {
		age int
		m   map[string]string
	}{age: 11, m: map[string]string{"a": "1"}}

	if sm1 == sm2 {
		fmt.Println("sm1 == sm2")
	}
	//if reflect.DeepEqual(sm1,sm2) {
	//	fmt.Println("sm1 == sm2")
	//}
}

结果 编译出错

invalid operation: sm1 == sm2 (struct containing map[string]string cannot be compared)

解析
结构体相同的条件有
内部属性个数,顺序相同,注意顺序不同也不相等 内部属性可以比较,像map,slice就不能比较,不能使用“==”比较,如果比较可以使用注释代码实现map比较
【nil】
1.nil不能做string的空值使用

package main

import (
	"fmt"
)

func GetValue(m map[int]string, id int) (string, bool) {
	if _, exist := m[id]; exist {
		return "存在数据", true
	}
	return nil, false
}
func main()  {
	intmap:=map[int]string{
		1:"a",
		2:"bb",
		3:"ccc",
	}

	v,err:=GetValue(intmap,3)
	fmt.Println(v,err)
}

结果 编译不通过

cannot use nil as type string in return argument

nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”,string的空值是“”
2.nil赋值于某个对象

var x = nil

结果 编译出错,nil 必须给一个明确类型的值赋值 比如 var x *int=nil
【iota】

package main

import (
	"fmt"
)

const (
	x = iota
	y
	z = "zz"
	k
	p = iota
)

func main()  {
	fmt.Println(x,y,z,k,p)
}

结果

0 1 zz zz 4

解析 iota是特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

【const】
1.常量定义格式

const identifier [type] = value

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型 显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
还可以多重赋值 const a, b, c = 1, false, "str" //多重赋值
根据上面所说,下面正确的是ABD C error变量不能做常量

A.
const Pi float64 = 3.14159265358979323846
const zero= 0.0
B.
const (
size int64= 1024
eof = -1
)
C.
const (
ERR_ELEM_EXISTerror = errors.New("element already exists")
ERR_ELEM_NT_EXISTerror = errors.New("element not exists")
)
D.
const u, vfloat32 = 0, 3
const a,b, c = 3, 4, "foo"

2.常量取地址

package main
const cl  = 100

var bl    = 123

func main()  {
	println(&bl,bl)
	println(&cl,cl)
}

结果 编译不通过

cannot take the address of cl

解析 常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用
【:=】

package main
var(
    size :=1024
    max_size = size*2
)
func main()  {
    println(size,max_size)
}

结果 编译不通过

syntax error: unexpected :=, expecting =

解析 简短模式定义变量只能函数内部使用,去掉:就可以编译成功了 【goto】

package main

func main()  {

	for i:=0;i<10 ;i++  {
	loop:
		println(i)
	}
	goto loop
}

结果 编译不通过

goto loop jumps into block starting at .\main.go:5:22

解析 goto不能跳转到其他函数或者内层代码,这里跳进了循环里面,编译不通过

【type alias】
1.definition&alias

package main
import "fmt"

func main()  {
	type MyInt1 int
	type MyInt2 = int
	var i int =9
	var i1 MyInt1 = i
	var i2 MyInt2 = i
	fmt.Println(i1,i2)
}

结果 编译不通过

cannot use i (type int) as type MyInt1 in assignment

解析
**Go 1.9 新特性 Type Alias ** 基于一个类型创建一个新类型,称之为defintion;基于一个类型创建一个别名,称之为alias。 MyInt1为称之为defintion,虽然底层类型为int类型,但是不能直接赋值,需要强转; MyInt2称之为alias,可以直接赋值
2.定义方法

package main
import "fmt"

type User struct {
}
type MyUser1 User
type MyUser2 = User
func (i MyUser1) m1(){
	fmt.Println("MyUser1.m1")
}
func (i User) m2(){
	fmt.Println("User.m2")
}

func main() {
	var i1 MyUser1
	var i2 MyUser2
	i1.m1()
	i2.m2()
}

结果

MyUser1.m1
User.m2

解析 MyUser1是definition,和user不同的另一种类型,他有自己的方法m1可以用,MyUser2是int的别名,可以使用user的方法M2,但是i1.m2()就不行
3.重复结构

package main

import "fmt"

type T1 struct {
}
func (t T1) m1(){
	fmt.Println("T1.m1")
}
type T2 = T1
type MyStruct struct {
	T1
	T2
}
func main() {
	my:=MyStruct{}
	my.m1()
}

结果 编译不通过

ambiguous selector my.m1

解析 只要有重复的方法、字段,就会有这种提示,因为不知道该选择哪个 【全局变量】

定义一个包内全局字符串变量,下面语法正确的是()
A. var str string
B. str := ""
C. str = ""
D. var str = ""

答案

AD

解析 短声明操作符不能作用于函数外部
C会报错

syntax error: non-declaration statement outside function body

【类型转换】
1.

package main
func main(){
	type MyInt int
	var i int = 1
	var j MyInt = MyInt(i)
	println(j)
}

这样是对的,(MyInt)i会编译报错 想用i.(MyInt)也可以,但是i必须是interface{}类型,所以修改

func main(){
	type MyInt int
	var i interface{} = 1
	var j MyInt = i.(MyInt)
	println(j)
}
var b bool
b=1
b=bool(1)
var int i
if i //wrong

两种赋值都是错的,在golang中int 和bool类型不能相互转换,但是在C++中是可以的
【初始化】 局部变量的几种初始化方式

var i int = 1
var i = 1
i := 1

注意i=1是不对的

【引用类型】

变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配。通过GC回收。
指针、slice、mapchaninterface等都是引用类型。

【new和make】
相同点
都是分配内存,初始化类型
不同点
作用对象不同,new 都可以 make只能用于内置引用类型slice,map,channel(interface{}也不行)
返回值不同
new返回的是初始化零值之后变量的地址,make返回的是变量的引用,当将该变量当做函数参数时,函数内部修改会影响外部变量的值
函数不同

func make(t Type, size ...IntegerType) Type
func new(Type) *Type

new只需要一个类型即可,make 类型是切片时还需要第二个参数,类型个数,甚至还可以有第三个 make([]int,10) 一个int数组,里面10个元素值为0
make([]int,0,10) 一个数组,参数2是len目前元素个数,参数3是cap数组容量,所以元素个数现在为0 ,但能装10个
make([]int,5,10) 已经有5个元素值为0,能装10个 类型是阻塞通道或者map则不需要 make(chan int) make(map[string]int) 非阻塞通道则需要说明通道能装多少元素 make(chan int,2)

【++操作】

Go中 ++ 和 -- 只能作为语句而非表达式
a := 1
a ++  // 注意:不能写成 ++ a 或 -- a 必须放在右边使用
b := a++ // 此处为错误的用法,不能写在一行,要单独作为语句使用

【sync.RWMutex和sync.Mutex】
Mutex互斥锁,Lock()加锁,Unlock()解锁,使用Lock()加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁.适用于读写不确定场景,即读写次数没有明显的区别,并且只允许只有一个读或者写的场景,所以该锁叶叫做全局锁
RWMutex读写锁,该锁可以加多个读锁或者一个写锁,其经常用于读次数远远多于写次数的场景 如果在添加写锁之前已经有其他的读锁和写锁,则lock就会阻塞直到该锁可用
读锁,当有写锁时,无法加载读锁,当只有读锁或者没有锁时,可以加载读锁,读锁可以加载多个,所以适用于"读多写少"的场景
【map】

// 先声明map
var m1 map[string]string
// 再使用make函数创建一个非nil的map,nil map不能赋值
m1 = make(map[string]string)
// 最后给已声明的map赋值
m1["a"] = "aa"
m1["b"] = "bb"

// 直接创建
m2 := make(map[string]string)
// 然后赋值
m2["a"] = "aa"
m2["b"] = "bb"

// 初始化 + 赋值一体化
m3 := map[string]string{
	"a": "aa",
	"b": "bb",
}

// ==========================================
// 查找键值是否存在
if v, ok := m1["a"]; ok {
	fmt.Println(v)
} else {
	fmt.Println("Key Not Found")
}

// 遍历map
for k, v := range m1 {
	fmt.Println(k, v)
}

1.判断channel是否断开 因为close的channel不会阻塞,并返回类型的nil值,会导致死循环

package main

import (
    "fmt"
)

func main() {
    c := make(chan int, 10)
    c <- 1
    c <- 2
    c <- 3
    close(c)

    for {
        fmt.Println(<-c)
    }
}   

判断短chan是否关闭

package main

import (
    "fmt"
)

func main() {
    c := make(chan int, 10)
    c <- 1
    c <- 2
    c <- 3
    close(c)

    for {
        i, ok := <-c
        if !ok {
            fmt.Println("channel closed!")
            break
        }
        fmt.Println(i)
    }
}

1.关闭通道close

ch := make(chan int)
close(ch)
ch<-9

上面会panic,往一个关闭的通道里面写数据会panic

ch := make(chan int)
ch<-9
close(ch)
for{
		i := <-ch
		print(i)
}

从一个关闭的通道读数据,会先读取已经发送到通道的值,然后会不断得到对应类型的零值,这里就会得到无数的0(打印9000000000...)

1.for range 能够判断channel是否关闭,通道关闭会停止阻塞,跳出循环

func main() {
	c := make(chan int)
	go func() {
		for i := 0; i < 10; i = i + 1 {
			c <- i
		}
		close(c)
	}()
	for i := range c {
		fmt.Println(i)
	}
	fmt.Println("Finished")
}

会依次打印出来0-9,然后打印“Finished”,因为gorountine中关闭了通道,for range得知通道关闭就不再阻塞,程序往下进行 如果去掉close,for循环会一直阻塞,等到数据到来,“Finished“永远执行不到

for range 会把关闭后的通道里面的值全都拿出来之后才会跳出循环,并不会直接跳出来

【goroutine退出机制】

goroutine设计的退出机制是由goroutine自己退出,不能在外部强制结束一个正在执行的goroutine(只有一种情况正在运行的goroutine会因为其他goroutine的结束被终止,就是main函数退出或程序停止执行)

image.png

尽管标准库在遇到管道/函数等无法被序列化的内容时会发生错误,但因为本题中 d 和 e 均为小写未导出变量,因此不会发生序列化错误。
golang中大多数数据类型都可以转化为有效的JSON文本,除了channel、complex、函数等。

在golang指针中可进行隐式转换,对指针取值,对所指对象进行序列化。

【进制】

package main

import (
   "fmt"
)

func main() {
   fmt.Println(09)
}

结果 会报错 invalid digit '9' in octal literal
octal literal 是8进制的意思,8进制中只有0-7 所以9会报错,改成07 都不会报错

【new make区别】

newmake
作用对象任意类型slice,map,channel类型
返回指向内存地址的指针引用类型本身
内存操作分配内存,没有初始化分配,初始化内存

逃逸分析

定义

编译器根据代码的特征和生命周期,自动的把变量分配到堆或者是栈上面

时机

在编译阶段,由编译器执行

对于go1.6以上版本,如果出现【并发map读写】程序会直接以fatal error崩溃,即使同routine内有recover()也不能恢复

image.png