必看的题目:https://blog.csdn.net/weiyuefei/article/details/77963810
1、关于值传递、引用传递与指针传递
false : bool,
0: integer
0.0: float
"": string
nil : pointer, function, interface, slice, channel, map
对于复合类型, go语言会自动递归地将每一个元素初始化为其类型对应的零值。比如:数组, 结构体。
nil 是专门为go语言的指针类型和引用类型准备的,go语言的数组和结构体可是值类型, 切片、字典或通道为引用类型。所以如果数组作为函数参数时,因为是值类型, 所以要复制 。
nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”
在go语言中没有引用传递,只有值传递。
例1:值传递
func main() {
a := []string{"a", "b"}
test(a)
fmt.Println(a)
}
func test(b []string) {
b = []string{"cc", "cc"}
}
打印出来后值为:[a b],表明在传递时是复制了一份单独的数组结构。
指针传递也是值传递。
例2:指针传递,也是值传递
func main() {
a := &[]string{"a", "b"}
test(a)
fmt.Println(*a)
}
func test(b *[]string) {
b = nil
}
打印出来后为:[a b]。传递指针时,能修改指针所指向的值,并不能修改指针本身的值。
传引用
var a Object
modify(a) // 修改a的值
print(a)
如果函数modify
修改a
的值, 然后print
打印出来的也是修改后的值,那么就可以认为modify是通过引用的方式使用了参数a。而如上例子证明了指明传递是值传递。
例3:引用传递
func main() {
a := new(int)
fmt.Println(a)
func() {
a = nil
}()
fmt.Println(a)
}
打印结果为:
0xc042008220
关于指针传递与引用传递参考:https://studygolang.com/articles/4810
2、关于go中的map
go语言中的map不是协程安全的,如果要多个协程对同 一个map进行写操作,则会出错:
fatal error: concurrent map writes
举例:
func main() {
Map := make(map[int]int)
for i := 0; i < 100000; i++ {
go writeMap(Map, i, i)
go readMap(Map, i)
}
}
func readMap(Map map[int]int, key int) int {
return Map[key]
}
func writeMap(Map map[int]int, key int, value int) {
Map[key] = value
}
因为map为引用类型,所以即使函数传值调用,参数副本依然指向映射m, 所以N个goroutine并发写同一个映射m,共享资源会遭到破坏。
解决办法就是加锁,或者channel排队串行化。
例2:
type S struct {
I int
}
func main() {
m := map[string]S{
"a": S{1},
}
m["a"].I = 5 // cannot assign to struct field m["a"].I in map
}
对于一个struct值的map,你无法更新单个的struct值。因为只有变量才可以赋值,变量的定义里包括了array和slice的下标表达式,但是不包括map的下标表达式。
go中的map输出是无序的,如下:
m := make(map[int]int)
m[0] = 0
m[1] = 1
m[2] = 2
m[3] = 3
m[4] = 4
for k,v := range m{
fmt.Printf("map[%d = %d\n",k,v)
}
(4)map中的元素不是变量,因此不能寻址!!
参考:https://blog.csdn.net/erlib/article/details/50963152
3、关于初始化make与new
1、make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分配。
2、new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点非常重要:new返回指针。
3、make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型(引用类型),而不是*T。本质来讲,导致这三个内建类型有所不同的原因是:引用在使用前必须被初始化。例如,一个slice,是一个包含指向数据(内部array)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slice为nil。对于slice、map和channel来说,make初始化了内部的数据结构,填充适当的值。make返回初始化后的(非零)值。
例1:
type X struct {
a int
}
type T struct {
i int;
f float64;
next *T
x X
y *X
}
则:
(1)new(T) 结果:&{0 0
(2)mySlice1 := make([]T, 1) 结果:[{0 0
(3)mySlice1 := make([]T, 2,5) 结果:[{0 0
(4)mySlice1 := make([]*T, 2,5) 结果: [
(5)t := new(*T) 则*t为nil
(6)t := new([]T) 结果:&[]
(7)t := new([]*T) 结果为:&[]
make在初始化一个切片时,如:
a := make([]int, 5, 10) // 注意这样是初始化了一个切片类型
则第一个参数为len,第二个为cap。长度是指已经被赋过值的最大下标+1,可通过内置函数len()获得。容量是指切片目前可容纳的最多元素个数,可通过内置函数cap()获得
4、数组Array与切片Slice
数组举例如下:
var arr [5]int
var arr [5]int{1,2,3,4,5}
var arr [...]int{1,2,3,4,5}
数组只是连续的内存块,如果你去阅读 Go 运行时的源码(src/runtime/malloc.go),你会发现创建一个数组本质上就是分配了一块指定大小的内存。数组元素总是会初始化为指定类型的 零值
切片举例如下:
var foo []int
切片的数据结构包括 3 个部分 - 指向数组的指针、切片的长度和切片的容量:
type slice struct {
array unsafe.Pointer
len int
cap int
}
当创建一个新的切片时,Go 运行时会在内存里创建这样一个包含 3 块区域的对象,并且会把数组指针初始化为 nil
,len
和 cap
初始化为 0。
可以用 make
来初始化一个指定大小的切片:
foo = make([]int, 5)
这段代码会创建一个切片,包含了一个 5 个元素的数组,每个元素的初值为 0,len
和 cap
的初值则为 5。
Cap 是指切片大小可以达到的上限,以便为未来可能的增长留出空间。可以用 make([]int, len, cap)
语法来指定容量。
关于切片与数组需要注意:
(1)如果你要更改切片中某些元素的值,实际上是在改变切片指向的数组元素的值。
举例:
func main() {
foo := make([]int, 5)
foo[3] = 42
foo[4] = 100
fmt.Println(foo)
bar := foo[1:4]
bar[1] = 99
fmt.Println(foo)
fmt.Println(bar)
}
打印结果如下:
[0 0 0 42 100]
[0 0 99 42 100]
[0 99 42]
(2)切片函数append 注意是追加
例2:
func main() {
s := make([]int, 3)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
结果如下:[0 0 0 1 2 3]
(3) 数组扩容
当切片的容量不够用时,会开辟一个新的数组,然后将原来数组中的值复制到新数组中,这样就改变了切片指向的底层数组:
a := make([]int, 16)
b1 := a[1:8] // 在a[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]上进行切片操作
a = append(a, 1) // 追加元素导致底层的数组变为了新的,而旧的仍然为16个0
a[2] = 42
fmt.Println(a) // [0 0 42 0 0 0 0 0 0 0 0 0 0 0 0 0 1]
b2 := a[1:8] // 在新的上进行切片操作,此时的a为[0 0 42 0 0 0 0 0 0 0 0 0 0 0 0 0 1]
fmt.Println(b1) // [0 0 0 0 0 0 0]
fmt.Println(b2) // [0 42 0 0 0 0 0]
由于切片由于底层是指向数组,所以无法知道这个切片到底耗费了多少内存。
5、for range
转载地址: https://www.cnblogs.com/hetonghai/p/6718250.html
go只提供了一种循环方式,即for循环,在使用时可以像c那样使用,也可以通过for range方式遍历容器类型如数组、切片和映射。但是在使用for range时,如果使用不当,就会出现一些问题,导致程序运行行为不如预期。比如,下面的示例程序将遍历一个切片,并将切片的值当成映射的键和值存入,切片类型是一个int型,映射的类型是键为int型,值为*int,即值是一个地址。
package main
import "fmt"
func main() {
slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)
for index, value := range slice {
myMap[index] = &value
}
fmt.Println("=====new map=====")
prtMap(myMap)
}
func prtMap(myMap map[int]*int) {
for key, value := range myMap {
fmt.Printf("map[%v]=%v\n", key, *value)
}
}
运行程序输出如下:
=====new
map
=====
map
[3]=3
map
[0]=3
map
[1]=3
map
[2]=3
=====new
map
=====
map
[0]=0
map
[1]=1
map
[2]=2
map
[3]=3
但是由输出可以知道,映射的值都相同且都是3。其实可以猜测映射的值都是同一个地址,遍历到切片的最后一个元素3时,将3写入了该地址,所以导致映射所有值都相同。其实真实原因也是如此,因为for range创建了每个元素的副本,而不是直接返回每个元素的引用,如果使用该值变量的地址作为指向每个元素的指针,就会导致错误,在迭代时,返回的变量是一个迭代过程中根据切片依次赋值的新变量,所以值的地址总是相同的,导致结果不如预期。
package main
import "fmt"
func main() {
slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)
for index, value := range slice {
num := value
myMap[index] = &num
}
fmt.Println("=====new map=====")
prtMap(myMap)
}
func prtMap(myMap map[int]*int) {
for key, value := range myMap {
fmt.Printf("map[%v]=%v\n", key, *value)
}
}
运行程序输出如下:
=====new
map
=====
map
[2]=2
map
[3]=3
map
[0]=0
map
[1]=1
type Job struct {
dsp_id string
out chan Job
}
func main() {
jobs := []Job{}
num := 2
for i:= 0;i
输出结果为:
{i
{i
6、defer、return和返回值之间的执行顺序
func main() {
fmt.Println("return:", a()) // 打印结果为 return: 0
fmt.Println("return:", b()) // 打印结果为 return: 2
fmt.Println("c return:", *(c())) // 打印结果为 c return: 2
}
func a() int {
var i int
defer func() {
i++
fmt.Println("defer2:", i) // 打印结果为 defer: 2
}()
defer func() {
i++
fmt.Println("defer1:", i) // 打印结果为 defer: 1
}()
// 函数的返回值没有被提前声明,其值来自于其他变量的赋值,
// 而defer中修改的也是其他变量,而非返回值本身,因此函数
// 退出时返回值并没有被改变。
return i
}
func b() (i int) {
defer func() {
i++
fmt.Println("defer2:", i) // 打印结果为 defer: 2
}()
defer func() {
i++
fmt.Println("defer1:", i) // 打印结果为 defer: 1
}()
// 函数的返回值被提前声明,也就意味着defer中是可以调用到真实
// 返回值的,因此defer在return赋值返回值 i 之后,再一次地修改
// 了 i 的值,最终函数退出后的返回值才会是defer修改过的值。
return i // 或者直接 return 效果相同
}
func c() *int {
var i int
defer func() {
i++
fmt.Println("c defer2:", i) // 打印结果为 c defer: 2
}()
defer func() {
i++
fmt.Println("c defer1:", i) // 打印结果为 c defer: 1
}()
// 虽然 c()*int 的返回值没有被提前声明,但是由于 c()*int 的
// 返回值是指针变量,那么在return将变量 i 的地址赋给返回值后,
// defer再次修改了 i 在内存中的实际值,因此函数退出时返回值
// 虽然依旧是原来的指针地址,但是其指向的内存实际值已经被成功修改了。
return &i
}
需要掌握2个要点:
- 多个defer的执行顺序为“后进先出”;
- defer、return、返回值三者的执行逻辑应该是:return最先执行,return负责将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出。
7、一定要理解go的接口与实现类
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 main() {
var peo People = &Stduent{} // 注意这里必须Student的对象的指针类型
think := "bitch"
fmt.Println(peo.Speak(think))
}
8、比较结构体类型
// 进行结构体比较时候,只有相同类型的结构体才可以比较,
// 结构体是否相同不但与属性类型个数有关,还与属性顺序相关
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"}}
// 报错,结构体中有不可比较的类型,如引用类型map与slice
//if sm1 == sm2 {
// fmt.Println("sm1 == sm2")
//}
if reflect.DeepEqual(sm1, sm2) {
fmt.Println("sm1 == sm2") // 打印
} else {
fmt.Println("sm1 != sm2")
}
}
9、go字符串的遍历输出
https://blog.csdn.net/benben_2015/article/details/78904860