go陷阱
必看的题目: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
<nil>
关于指针传递与引用传递参考: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 <nil> {0} <nil>}
(2)mySlice1 := make([]T, 1) 结果:[{0 0 <nil> {0} <nil>}]
(3)mySlice1 := make([]T, 2,5) 结果:[{0 0 <nil> {0} <nil>} {0 0 <nil> {0} <nil>}]
(4)mySlice1 := make([]*T, 2,5) 结果: [<nil> <nil>]
(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<num;i++{ job := Job{ dsp_id:"i" } jobs = append(jobs,job) } jobChannel := make(chan Job,num) for _,j := range jobs{ j.out = jobChannel } for _,j := range jobs{ fmt.Println(j) // 打印结果没有out值 } }
输出结果为:
{i <nil>}
{i <nil>}
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
go陷阱的更多相关文章
- 你可能不知道的陷阱, IEnumerable接口
1. IEnumerable 与 IEnumerator IEnumerable枚举器接口的重要性,说一万句话都不过分.几乎所有集合都实现了这个接口,Linq的核心也依赖于这个万能的接口.C语言的 ...
- java笔记--笔试中极容易出错的表达式的陷阱
我相信每一个学过java的人儿们都被java表达式虐过,各种"肯定是它,我不可能错!",然后各种"尼玛,真假,怎么可能?",虽然在实际开发中很少会真的让你去使用 ...
- 【Swift】iOS UICollectionView 计算 Cell 大小的陷阱
前言 API 不熟悉导致的问题,想当然的去理解果然会出问题,这里记录一下 UICollectionView 使用问题. 声明 欢迎转载,但请保留文章原始出处:) 博客园:http://www.cn ...
- JavaScript中的this陷阱的最全收集
JavaScript来自一门健全的语言,所以你可能觉得JavaScript中的this和其他面向对象的语言如java的this一样,是指存储在实例属性中的值.事实并非如此,在JavaScript中,最 ...
- 高性能MySQL(四):schema陷阱
一.schema陷阱 二.缓存表和汇总表 三.范式和反范式
- C#_闭包陷阱
如果匿名方法(Lambda表达式)引用了某个局部变量,编译器就会自动将该引用提升到该闭包对象中. 即将for循环中的变量i修改成了引用闭包对象的公共变量i.这样一来,即使代码执行后离开了原局部变量i的 ...
- 安装 Linux 时碰到的硬盘分区的陷阱及应对
硬盘分区的陷阱及应对 之所以想到写这篇,是因为本人在折腾 Linux 系统的过程中,有多次掉入硬盘分区的陷阱的经历.最近几天,再一次掉入坑中,折腾了两天才从坑中爬出来.经过多方查询资料,终于弄明白了硬 ...
- NULL的陷阱:Merge
NULL表示unknown,不确定值,所以任何值(包括null值)和NULL值比较都是不可知的,在on子句,where子句,Merge或case的when子句中,任何值和null比较的结果都是fals ...
- 洛谷P1156 垃圾陷阱[背包DP]
题目描述 卡门――农夫约翰极其珍视的一条Holsteins奶牛――已经落了到“垃圾井”中.“垃圾井”是农夫们扔垃圾的地方,它的深度为D(2<=D<=100)英尺. 卡门想把垃圾堆起来,等到 ...
- JavaScript中的this陷阱的最全收集 没有之一
当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概 念,可能真解释不清楚.有句话这么说:如果你不能向一个6岁小孩解 ...
随机推荐
- LDA背景资料
[https://zhuanlan.zhihu.com/p/30226687] LDA模型的前世今生 在文本挖掘中,有一项重要的工作就是分析和挖掘出文本中隐含的结构信息,而不依赖任何提前标注的信息.L ...
- scrapy系列(三)——基础spider源码解析
前面两章介绍了scrapy的安装和项目的新建,那么这一章就讲讲spider吧. scrapy有个命令是runspider, 这个命令的作用就是将一个spider当做一个python文件去执行,而不用创 ...
- Windows10 内存泄漏
之前遇到win10开机idle一段时间后, 内存噌噌的往上彪, 16G内存基本什么东西没开就90%多.查了网上的一些解决方案: 方法1. 关闭Ndu服务 sc config Ndu start=dis ...
- Linux 小知识翻译 - 「Shell 脚本」
这次说说「Shell 脚本」. 根据上回的介绍,Shell就是「作为联系Linux和用户的接口而存在的软件」.在Linux环境中,通过Shell来操作系统很普遍. 这里,考虑到有时候可能想要「多次的进 ...
- [BUG]Appium1.9.1 这个问题竟然花了我5分钟进行定位
1.先上问题,知道是什么问题先 EE ====================================================================== ERROR: tes ...
- centos 上安装phpstorm
phpstorm在centos上运行依赖JDK,所以先安装JDK环境. 假如是centos自带的openjdk,直接卸载,不支持phpstorm. 下载jdk-7u45-linux-i586.tar. ...
- vue框架简介
MVVM框架概述 什么是vue 是一套构建用户界面的渐进式(用到哪一块就用哪一块,不需要全部用上)前端框架,Vue 的核心库只关注视图层 vue的兼容性 Vue.js 不支持 IE8 及其以下版本,因 ...
- 【BZOJ3930】选数
[BZOJ3930]选数 Description 我们知道,从区间[L,H](L和H为整数)中选取N个整数,总共有(H-L+1)^N种方案.小z很好奇这样选出的数的最大公约数的规律,他决定对每种方案选 ...
- python五十九课——正则表达式的拓展内容
演示正则表达式的拓展内容:函数:finditer(regex,string,[flags=0]):参数:和match.search.findall一样理解功能:将所有匹配的数据封装为一个一个的matc ...
- MongoDB 4.6.1 c++ driver 编译
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/sheismylife/article/details/25512251 这个版本号已经和之前不一样了 ...