Welcome to Rooeye's blog

go 易混淆点梳理 (一)

golang rooeye 129℃

1. 当给多个变量同时赋值时:

 i, data := 0, []int{1,2,3}
 i, data[i]= 2, 100

执行后:

i=2,data[0]=100

2. 未使用的变量会引发编译错误,但是未使用的常量可以正常编译执行

 func main() {
	const m int = 1
 }

代码可以正常运行,常量 m 如果是全局的也可以正常运行

3. iota关键字定义枚举类型时,从0开始,按行递增

 const (
	a0   = iota // 0
	str0 = "hi"
	a1   = iota
 )

a1 的值是2,不是1

4. 定义变量时支持八进制,16进制,科学计数法

a, b, c := 010, 0x10, 1e2
fmt.Println(a, b, c) // 8 16 100

5. 字符串换行时如果使用加号连接,加号应该放在上一行

str := "Hi," +
"Helen!" // Hi,Helen!

str2 := "Hi,"
+"Helen!    // invalid operation: + untyped string

6. 遍历字符串有两种方式,一种是普通的for循环,一种是 for…range 的方法,当字符串中含有中文的时候,应该使用 for… range 的方式遍历

str := "I Love U 中国!"
fmt.Println(len(str)) //18

//byte
for i := 0; i < len(str); i++ {
   fmt.Printf("%c ", str[i])     //汉字打印乱码 I   L o v e   U   ä ¸ ­ å › ½ ï ¼   
}

fmt.Println()

//rune
for _, v := range str {
   fmt.Printf("%c ", v)       //I   L o v e   U   中 国 !
}

7. 初始化复合类型(slice,map,struct)对象时,如果有多行,最后一行结尾必须是 ,或者 } , 否则会编译失败

slice 正确的写法:

arr1 := []int{
	1,
	2,
}

arr2 := []int{
        1,
	2}

不正确的写法:

arr1 := []int{
	1,
	2
}

map 正确的写法:

map1 := map[int]int{
		1: 1,
		2: 2,
}

map2 := map[int]int{
		1: 1,
		2: 2}

不正确的写法:

map2 := map[int]int{
		1: 1,
		2: 2
}

struct 正确的写法:

struct1 := struct {
		x int
		y int
}{
		100,
		200,
}

struct2 := struct {
		x int
		y int
}{
		100,
		200}

不正确的写法:

struct2 := struct {
		x int
		y int
}{
		100,
		200
}

8. 数组是值类型,切片是引用类型,在 for … range 遍历数组的时候,会拷贝一份新的数组,在 for … range 遍历切片的时候,会拷贝一份 slice 的结构体(struct slice {pointer,len,cap}),但是其底层数组并不会被拷贝

range遍历数组的时候用的是新的拷贝,所以循环体内对 a 的更改不会影响到 v 的值:

a := [3]int{0, 1, 2}
for _, v := range a {
	a[0], a[1], a[2] = 3, 4, 5
	fmt.Println(v). //0 1 2
}
fmt.Println(a) // 3 4 5

range遍历切片的时候不会拷贝底层元素,对 a 的更改会影响到 v 的值:

a := []int{0, 1, 2}
for _, v := range a{
	a[0], a[1], a[2] = 3, 4, 5
	fmt.Println(v). //0 4 5
}
fmt.Println(a) // 3 4 5

9. 对于 channel 类型,长度(len)代表已经存入通道的元素数量,容量(cap)代表整个通道缓冲区的大小

 //非缓冲通道 长度=容量
c1 := make(chan int)
fmt.Println(len(c1), cap(c1)) //0 0

//缓冲通道 len代表通道中已存入的元素数量,cap代表整个缓冲区的大小
c2 := make(chan int, 2)
fmt.Println(len(c2), cap(c2)) //0 2
c2 <- 1
fmt.Println(len(c2), cap(c2)) //1 2

//缓冲通道 当缓冲通道已满的时候,len = cap
c2 <- 2
fmt.Println(len(c2), cap(c2)) //2 2

10. 对于 channel 类型,如果长度不为0,异步读/写时,对于值类型会发生深拷贝(比如结构体,数组对象会重新分配一块内存),对于引用类型(slice,map...) 是浅拷贝,底层的数据结构不会被复制

ch := make(chan Student, 1)
stu1 := Student{"Bob"}
ch <- stu1
stu2 := <-ch
fmt.Printf("stu1:%p  stu2:%p\n", &stu1, &stu2) //stu1:0xc00000e1f0  stu2:0xc00000e200
 //
ch := make(chan []int, 1)
s1 := []int{1, 2, 3}
ch <- s1
s2 := <-ch

s2[0] = 100
fmt.Println(s1, s2) //[100 2 3] [100 2 3]

//
ch2 := make(chan [3]int, 1)
s3 := [3]int{1, 2, 3}
ch2 <- s3
s4 := <-ch2

s3[0] = 100
fmt.Println(s3, s4) //[100 2 3] [1 2 3]

//
ch3 := make(chan map[int]int, 1)
m := make(map[int]int)
m[1] = 1
ch3 <- m
m2 := <-ch3
m[1] = 3
fmt.Println(m, m2) // map[1:3] map[1:3]

11. 匿名函数用法:1.赋值给变量 2.作为切片元素类型 3.作为结构体成员 4. 作为 channel 类型

赋值给变量:

fn := func(a, b int) int { return a + b }
fmt.Println(fn(1, 2)) //3

作为切片元素类型:

type abFunc func(a, b int) int
funcArr := []abFunc{
	func(a, b int) int {
		return a + b
	},
	func(a, b int) int {
		return a * b
	},
}
fmt.Println(funcArr[1](2, 4)) //8

作为结构体成员:

t := struct {
	fn func(int) int
}{
	fn: func(a int) int {
		return 2 * a
	},
}
fmt.Println(t.fn(1)) //2

通过 channel 传输:

ch := make(chan func(int) int, 1)
ch <- func(a int) int {
	return 2 * a
}
cfn := <-ch
fmt.Println(cfn(1)) //2

12. 当匿名函数作为参数返回时,返回的是一个 FuncVal 的对象,其中包括匿名函数的地址和闭包对象的指针

func test() func() {
        a := 1
	return func() {
		fmt.Println(a)
	}
}

func main() {
	fn := test()
	fn()  //1
}

13. defer 延迟调用,采用 FILO 的模式,如果有多个延迟调用,当某个延迟调用出错时,剩余 defer 仍然会被调用

// First in, Last Out
func test() {
	defer func() { fmt.Println("a") }()
	defer func() { fmt.Println("b") }()
	defer func() { panic("err!") }()
	defer func() { fmt.Println("d") }()
}

func main() {
	test()
}
d
b
a
panic: err!

14. 代码中出现 panic 的时候,已注册的 defer 正常执行,未注册的 defer 不会执行

// First in, Last Out
func test() {
	defer func() { fmt.Println("a") }()
	defer func() { fmt.Println("b") }()
	panic("err!")
	defer func() { fmt.Println("c") }()
}

func main() {
	test()
}
b
a
panic: err!

15. defer 用作代码结束进行错误处理时,可以用 recover() 方法对错误进行捕获

func errProcess() {
        if err := recover(); err != nil {
		fmt.Println(err)
	}
}

func divide(a, b int) {
	defer errProcess()
	_ = a / b
	
}

func main() {
	divide(1, 0)
}

这里使用匿名函数也可以:

func divide(a, b int) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	_ = a / b
}

func main() {
	divide(1, 0)
}

输出:

runtime error: integer divide by zero

如果在panic抛出并且捕获后仍要执行部分业务逻辑,可以将上述过程封装到一个匿名函数中:

func divide(a, b int) {
	func() {
		defer func() {
			if err := recover(); err != nil {
				fmt.Println(err)
			}
		}()
		_ = a / b
	}()

	fmt.Println("operation ...")
}

func main() {
	divide(1, 0)
}

输出:

runtime error: integer divide by zero
operation ...

16. 切片进行 append 操作,超出容量后,若切片容量此时小于等于 1024,则容量翻倍,若大于1024,则容量变为原来的1.25倍,容量扩容多少和切片类型无关

for i := 1; i <= 2048; i++ {
	arr := make([]int, i, i)
	begin := cap(arr)
	fmt.Printf("%d ", begin)
	arr = append(arr, 1)
	end := cap(arr)
	fmt.Printf("%d %.2f\n", end, float32(end)/float32(begin))
}

运行结果:

1 2 2.00
2 4 2.00
3 6 2.00
.......
1021 2048 2.01
1022 2048 2.00
1023 2048 2.00
1024 1280 1.25
1025 1360 1.33
1026 1360 1.33
1027 1360 1.32
.......
2046 2560 1.25
2047 2560 1.25
2048 2560 1.25

转载请注明: Jinkun 的博客 » go 易混淆点梳理 (一)

喜欢 (0)