基础篇:从基础语法考察入手(一)
一、你觉得Golang是怎样一门语言?
- Golang 是一种高级且通用的编程语言,提供垃圾收集和并发编程的支持,具有非常强的静态类型。
- 在 Go 中,程序是通过使用有助于有效管理依赖关系的包构建的。它还使用编译链接模型从源代码生成可执行二进制文件。
- Go 是一种语法简单的语言,具有优雅且易于理解的语法结构。
- 它具有内置的强大标准库集合,可帮助开发人员解决很多问题,而无需第三方包。
- Go 对并发具有一流的支持,能够有效地利用多核处理器架构和内存。
二、Golang 是区分大小写还是不区分大小写?
区分大小写。
三、Golang 中 make 和 new 的区别?
new的要求传入一个类型,然后申请一个该类型大小的内存空间,并会初始化为对应的零值,返回指向该内存空间的一个指针。
new是用来分配内存的:
比如我们进行下面这段代码,新建一个指针却不用new给他分配内存:
func main() {
var p *int
*p = 2
fmt.Println(*p)
}
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x40b4f6]
就会panic,这是一个空指针。
如果我们给指针用new初始化一块内存:
func main() {
var p = new(int)
*p = 2
fmt.Println(*p)
}
输出结果就是:
0
2
都是切片、Map、通道Channel都是引用类型,其存储结构比较复杂,不是用new()分配一块内存,并简单的分配零值就行了。所以要用make来分配和初始化引用类型。
//Channel
ch := make(chan string)
go func() {
fmt.Println(<-ch)
}()
ch <- "hello"
close(ch)
//Map
m := make(map[string]int)
m["Hello"] = 2
fmt.Println(m)
//Slice
slice := make([]int, 2, 5)
slice = append(slice, 1)
fmt.Println(slice)
下面是运行结果:
hello
map[Hello:2]
[0 0 1]
总的来说:
- make和new都可用来分配内存,但是new只是将内存分配给变量,并给赋一个零值,而make不但分配了内存,还给引用类型初始化了一些相关属性(这里初始化这些相关属性时不再是简单的用默认零值来初始化了。
- make返回一个引用类型本身,而new返回一个指向零值内存空间的指针。
- make只能用于分配和初始化slice、map、channel类型,而new可以给一切类型。
四、不同类型的变量,其零值是什么?
不同类型,其零值是不同的,我们可以通过下面的这个例子了解一下:
package main
import "fmt"
func main() {
type stru struct {
s string
}
var ap *[3]int
var ip *int
var sp *string
var tp *stru
ap = new([3]int)
fmt.Println(*ap) //[0 0 0]
ip = new(int)
fmt.Println(*ip) // 0
sp = new(string)
fmt.Println(*sp) //
tp = new(stru)
fmt.Println(*tp) //{}
var a [3]int
var i int
var s string
var t stru
fmt.Println(a, i, s, t) //[0 0 0] 0 {}
}
下面我们来详细看一下复合类型的零值,先来数组:
func main() {
var array [5]int
fmt.Printf("array: %p %#v \n", &array, array)
var array_p = new([5]int)
fmt.Printf("array_p: %p %#v \n", &array_p, array_p)
(*array_p)[3] = 8
fmt.Printf("array_p: %p %#v \n", &array_p, array_p)
}
我们先来看看运行结果:
array: 0xc00000a3f0 [5]int{0, 0, 0, 0, 0}
array_p: 0xc000006030 &[5]int{0, 0, 0, 0, 0}
array_p: 0xc000006030 &[5]int{0, 0, 0, 8, 0}
再来看看切片:
var slice *[]int
fmt.Printf("a: %p %#v \n", &slice, slice)
sliceP := new([]int)
fmt.Printf("av: %p %#v \n", &sliceP, sliceP)
(*sliceP)[2] = 8
fmt.Printf("av: %p %#v \n", &sliceP, sliceP)
输出结果如下:
a: 0xc000006028 (*[]int)(nil)
av: 0xc000006038 &[]int(nil)
panic: runtime error: index out of range [2] with length 0
趁热打铁,再来看看Map的:
package main
import "fmt"
func main() {
var m map[string]string
fmt.Printf("m: %p %#v \n", &m, m)
mP := new(map[string]string)
fmt.Printf("mv: %p %#v \n", &mP, mP)
(*mP)["hello"] = "hello"
fmt.Printf("mv: %p %#v \n", &mP, mP)
}
运行结果如下:
m: 0xc0000ce018 map[string]string(nil)
mv: 0xc0000ce028 &map[string]string(nil)
panic: assignment to entry in nil map
还有通道channel:
cP := new(chan string)
fmt.Printf("cv: %p %#v \n", &cP, cP)
cP <- "hello"
invalid operation: cannot send to non-channel cP (variable of type *chan string)
为什么会报错呢?
这是因为切片、Map、通道Channel都是引用类型,其存储结构比较复杂,不是分配一块内存,并简单的给相关属性分配零值就行了(长度、指针…)。所以要用make来分配和初始化引用类型。
五、数组和切片的区别
- 切片其实本质上是对数组的封装。它包含,底层数组的指针、切片的长度和容量。
- 数组固定长度,而切片可扩容
- 数组如果数组长度相等,可以进行比较。而切片只能和nil进行比较,本质是因为数组是值类型,切片是引用类型。
var s []int
if s == nil {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
//输出结果为: equal
- 数组作为函数参数是进行值传递,函数内部改变数组元素不会对函数外部数组的函数值。而切片作为函数的参数值是进行指针传递,函数内部改变切片的值会影响函数外部切片的值。
六、for range 的时候它的地址会发生变化么?for 循环遍历 slice 有什么问题?
看一看下面这段代码:
package main
import "fmt"
func main() {
var slice = []int{1, 2, 3, 4, 5}
for i, v := range slice {
fmt.Printf("%d,%#v\n", i, &v)
}
}
运行结果如下:
0,(*int)(0xc000016098)
1,(*int)(0xc000016098)
2,(*int)(0xc000016098)
3,(*int)(0xc000016098)
4,(*int)(0xc000016098)
我们再来看看下面:
func main() {
var slice = []int{1, 2, 3, 4, 5}
fmt.Printf("%#v%#v%#v%#v%#v\n", &slice[0], &slice[1], &slice[2], &slice[3], &slice[4])
for i, v := range slice {
fmt.Printf("%#v,%#v\n", &i, &v)
}
fmt.Printf("%#v%#v%#v%#v%#v\n", &slice[0], &slice[1], &slice[2], &slice[3], &slice[4])
}
(*int)(0xc00000a3f0)(*int)(0xc00000a3f8)(*int)(0xc00000a400)(*int)(0xc00000a408)(*int)(0xc00000a410)
(*int)(0xc0000160c0),(*int)(0xc0000160c8)
(*int)(0xc0000160c0),(*int)(0xc0000160c8)
(*int)(0xc0000160c0),(*int)(0xc0000160c8)
(*int)(0xc0000160c0),(*int)(0xc0000160c8)
(*int)(0xc0000160c0),(*int)(0xc0000160c8)
(*int)(0xc00000a3f0)(*int)(0xc00000a3f8)(*int)(0xc00000a400)(*int)(0xc00000a408)(*int)(0xc00000a410)
可以得知,for range的时候,不是读取slice,然后直接输出里面的值。而是在遍历时开辟两块内存以值覆盖的方式将遍历得到的数据放入。由于这个特性,我们如果在for range里面开协程,不能直接把i、v的地址传给协程,也不能直接在for range内保存i,v的地址。如果遍历一个指针切片:
var slice = []int{1, 2, 3, 4, 5}
var slice_p = make([]*int, 0, 5)
for i, v := range slice {
fmt.Println(i, v)
slice_p = append(slice_p, &v)
}
fmt.Println(slice_p)
for i, v := range slice_p {
fmt.Println(i, *v)
fmt.Println(v)
}
那么结果将会是:
0 1
1 2
2 3
3 4
4 5
[0xc000016098 0xc000016098 0xc000016098 0xc000016098 0xc000016098]
0 5
0xc000016098
1 5
0xc000016098
2 5
0xc000016098
3 5
0xc000016098
4 5
0xc000016098
那么解决办法是什么?使用一个临时变量即可。
var slice = []int{1, 2, 3, 4, 5}
var slice_p = make([]*int, 0, 5)
for _, v := range slice {
vv := v
slice_p = append(slice_p, &vv)
}
fmt.Println(slice_p)
for i, v := range slice_p {
fmt.Println(i, *v)
fmt.Println(v)
}
[0xc0000aa058 0xc0000aa070 0xc0000aa078 0xc0000aa080 0xc0000aa088]
0 1
0xc0000aa058
1 2
0xc0000aa070
2 3
0xc0000aa078
3 4
0xc0000aa080
4 5
0xc0000aa088
另外我们也可以发现,当切片是指针切片时,切片里面是什么,for range遍历时还是切片里面的地址,不需要另外开辟。
七、go defer,多个 defer 的顺序,defer 在什么时机会修改返回值?
根据Golang官方文档描述,defer就像一个LIFO的栈,每次执行defer语句,都会将函数”压栈“,函数参数也会被保存下来;如果外层函数(非代码块)退出,最后的defer语句就会执行,也就是栈顶的函数或方法会被执行。
一条return语句,其实不是一条原子指令,其大概可以分为三条指令:
- 返回值为xxx
- 调用defer函数
- 空的return
package main
import (
"fmt"
)
func main() {
fmt.Println("return:", banana())
}
func banana() (i int) {
defer func() {
i++
fmt.Println("defer 2:", i)
}()
defer func() {
i++
fmt.Println("defer 1:", i)
}()
return i
}
defer 1: 1
defer 2: 2
return: 2
这是有名返回值的情况,接下我们来看一看匿名返回值的情况:
package main
import (
"fmt"
)
func main() {
fmt.Println("return:", apple())
}
func apple() int {
var i int
defer func() {
i++
fmt.Println("defer 2:", i)
}()
defer func() {
i++
fmt.Println("defer 1:", i)
}()
return i
}
defer 1: 1
defer 2: 2
return: 0
上面这两段代码说明了:defer语句只能访问有名返回值,不能直接访问匿名返回值。
但是如果是下面这种情况:
package main
import (
"fmt"
)
func main() {
fmt.Println("return:", banana())
}
func banana() (i int) {
defer func(i int) {
i++
fmt.Println("defer 2:", i)
}(i)
defer func(i int) {
i++
fmt.Println("defer 1:", i)
}(i)
return i
}
输出结果就为:
defer 1: 1
defer 2: 1
return: 0
这是因为传递给defer后面的匿名函数的是形参的一个复制值,不会影响实参i。