go小技巧(易错点)集锦_go问题经验-csdn博客


本站和网页 https://blog.csdn.net/General_zy/article/details/130618767 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

go小技巧(易错点)集锦_go问题经验-CSDN博客
go小技巧(易错点)集锦
最新推荐文章于 2024-04-25 11:53:18 发布
Generalzy
阅读量880
收藏
点赞数
分类专栏:
GO
文章标签:
golang
开发语言
后端
版权声明:本文为博主原创文章,遵循
CC 4.0 BY-SA
版权协议,转载请附上原文出处链接和本声明。
本文链接:
https://blog.csdn.net/General_zy/article/details/130618767
版权
专栏收录该内容
51 篇文章
5 订阅
订阅专栏
目录
len的魔力
评论区大佬解答
答案详解
结构体是否相等
答案解析:
结构体比较规则
举例
常量的编译
我的答案
标准答案
内存四区概念:
new关键字
答案
iota的魔力
结果
解析
可跳过的值
定义在一行
中间插队
小结
iota详解
iota 原理
iota 规则
依赖 const
按行计数
多个iota
空行处理
跳值占位
开头插队
一行多个iota
nil赋值
接口
答案解析
空接口
非空接口 iface
结构体json
json.Marshal 函数的文档
类型断言
channel
sync.WaitGroup
空map取值
注意
可变参数函数
可变参数
与传切片的区别
结构体嵌套接口
Go 没有继承,但可以通过内嵌类型模拟部分继承的功能。
实例
回到题目
切片的第三个参数
第三个参数
%+d
匿名结构体嵌套
defer的执行顺序
interface{}接口类型
切片扩容
图示
当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil
String()方法
map 的 value 是不可寻址的
解决方案
一个select死锁问题
相同问题
更多类似的问题
最典型的问题
循环中改变切片长度是否会死循环
python会
defer再探
range数组和range指针
结论
切片传参和切片扩容
range赋值地址
同理如下
range map
多重赋值的坑
如何判断map是否相等
DeepEqual部分源码
Go 1.15 中 var i interface{} = a 会有额外堆内存分配吗?
基准测试
GO结构体字典值拷贝和值引用
分析
package
main
const
=
"Go101.org"
// len(s) == 9
// 1 << 9 == 512
// 512 / 128 == 4
var
byte
<<
len
128
func
println
--
>>
>
len(s)若s为字符串常量或者简单的数组表达式,则len返回的为int型的
常量
,若s为不为上述情况(有函数计算、通道等),则len返回的为int型的
变量
关于位移操作,如果
常量位移表达式
的左侧操作数是一个无类型常量,那么其结果是一个整数常量,否则就是和左侧操作数同一类型的常量(必须是整数类型 );如果一个
非常量位移表达式
的左侧的操作数是一个无类型常量,那么它会先被隐式地转换为假如位移表达式被其左侧操作数单独替换后的类型。
“先被隐式地转换为假如位移表达式被其左侧操作数单独替换后的类型" :对于var b byte = 1 << len(s[:]),1 << len(s[:])会被隐式的转换为var b byte = 1(表达式被1单独替换)的类型,1为无类型常量,因为b为byte,所以1会被隐式转换为byte,所以1 << len(s[:])为byte类型。
s为常量表达式。对于var a byte = 1 << len(s) / 128,len(s)返回整型常量,所以1 << len(s)为常量表达式,且1为无类型常量,符合位移操作的第一种情况,所以为表达式结果为整型常量,故最后计算为4;
对于var b byte = 1 << len(s[:]) / 128,s[:]为函数计算,所以len(s[:])返回的为整型变量,所以1 << len(s[:]) / 128为非常量表达式,且1为无类型常量,符合位移操作的第二种情况,所以为表达式结果为byte,1<<9为512,转为byte类型,结果溢出,为0。
len 是一个内置函数,在官方标准库文档关于 len 函数 有这么一句:
For some arguments, such as a string literal or a simple array expression, the result can be a constant. See the Go language specification’s “Length and capacity” section for details.(当参数是字符串字面量和简单 array 表达式,len 函数返回值是常量)
内置函数 len 和 cap 获取各种类型的实参并返回一个 int 类型结果。实现会保证结果总是一个 int 值。如果 s 是一个字符串常量,那么 len(s) 是一个常量 。如果 s 类型是一个数组或到数组的指针且表达式 s 不包含信道接收或(非常量的) 函数调用的话, 那么表达式 len(s) 和 cap(s) 是常量,这种情况下,
s 是不求值的
。否则的话, len 和 cap 的调用结果
不是常量且 s 会被求值
第一句的 len(s) 是常量(因为 s 是字符串常量);而第二句的 len(s[:]) 不是常量。这是这两条语句的唯一区别:两个 len 的返回结果数值并无差异,都是 9,但一个是常量一个不是。
关于位移操作,在位移表达式的右侧的操作数必须为整数类型,或者可以被 uint 类型的值所表示的无类型的常量。如果一个非常量位移表达式的左侧的操作数是一个无类型常量,那么它会先被隐式地转换为假如位移表达式被其左侧操作数单独替换后的类型。
这里的关键在于常量位移表达式。根据上文的分析,1 << len(s) 是常量位移表达式,而 1 << len(s[:]) 不是。
如果常量位移表达式的左侧操作数是一个无类型常量,那么其结果是一个整数常量;否则就是和左侧操作数同一类型的常量(必须是整数类型 )
因此对于 var a byte = 1 << len(s) / 128,因为 1 << len(s) 是一个常量位移表达式,因此它的结果也是一个整数常量,所以是 512,最后除以 128,最终结果就是 4。
而对于 var b byte = 1 << len(s[:]) / 128,因为 1 << len(s[:]) 不是一个常量位移表达式,而做操作数是 1,一个无类型常量,
根据规范定义它是 byte 类型
(根据:如果一个非常量位移表达式的左侧的操作数是一个无类型常量,那么它会先被隐式地转换为假如位移表达式被其左侧操作数单独替换后的类型)。
所以 var b byte = 1 << len(s[:]) / 128 中,根据规范定义,1 会隐式转换为 byte 类型,因此 1 << len(s[:]) 的结果也是 byte 类型,而 byte 类型最大只能表示 255,很显然 512 溢出了,结果为 0,因此最后 b 的结果也是 0。
下面代码是否可以编译通过?为什么?
import
"fmt"
sn1
:=
struct
age
int
name
string
11
"qq"
sn2
if
==
fmt
Println
"sn1 == sn2"
sm1
map
"a"
"1"
sm2
"sm1 == sm2"
编译不通过。
只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。
结构体是相同的,但是结构体属性中有不可以比较的类型,如map,slice,则结构体不能用==比较。
sn3
sn3与sn1就不是相同的结构体了,不能用等于号比较。但可以使用reflect.DeepEqual进行比较
reflect
DeepEqual
else
"sm1 != sm2"
下面代码有什么问题?
cl
100
bl
123
&
这个还是比较简单的,因为常量在预处理的时候直接替换到代码中,相当于在某处直接写了一个常量值。
常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。
数据类型本质:固定内存大小的别名
数据类型的作用:编译器预算对象(变量)分配的内存空间大小。
内存四区:
栈区(Stack):
空间较小,要求数据读写性能高,数据存放时间较短暂。由编译器自动分配和释放,存放函数的参数值、函数的调用流程方法地址、局部变量等(局部变量如果产生逃逸现象,可能会挂在在堆区)
堆区(heap):
空间充裕,数据存放时间较久。一般由开发者分配及释放(但是Golang中会根据变量的逃逸现象来选择是否分配到栈上或堆上),启动Golang的GC由GC清除机制自动回收。
全局区-静态全局变量区:
全局变量的开辟是在程序在main之前就已经放在内存中。而且对外完全可见。即作用域在全部代码中,任何同包代码均可随时使用,在变量会搞混淆,而且在局部函数中如果同名称变量使用:=赋值会出现编译错误。全局变量最终在进程退出时,由操作系统回收。
全局区-常量区:
常量区也归属于全局区,常量为存放数值字面值单位,即不可修改。或者说的有的常量是直接挂钩字面值的。
10
cl是字面量
的对等符号。
所以在golang中,常量是无法取出地址的,因为字面量符号并没有地址而言。
下面这段代码能否通过编译?
list
new
append
过于简单,new()的返回值是[]int类型的指针且为nil,对nil不可操作
下面这段代码能否编译通过?如果可以,输出什么?
iota
"zz"
iota是golang语言的常量计数器,只能在常量的表达式中使用。
iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。
可以使用下划线跳过不想要的值。
OutMute AudioOutput
// 0
OutMono
// 1
OutStereo
// 2
OutSurround
// 5
Apple
Banana
+
Cherimoya
Durian
Elderberry
Fig
// Apple: 1
// Banana: 2
// Cherimoya: 2
// Durian: 3
// Elderberry: 3
// Fig: 4
iota 在下一行增长,而不是立即取得它的引用。
3.14
那么打印出来的结果是 i
iota初始值为0,出现一次const自增1
省略const语句时,代表与上一句相同
写完整是:
// 1 _代表不需要这个值
// zz iota=3
// zz iota=4
值得注意的是:iota是在一个const递增的
iota 的主要使用场景用于枚举。Go 语言的设计原则追求极尽简化,所以没有枚举类型,没有 enum关键字。
Go 语言通常使用常量定义代替枚举类型,于是 iota 常常用于其中,用于简化代码。
// 1 << (10*0)
KB
// 1 << (10*1)
MB
// 1 << (10*2)
GB
// 1 << (10*3)
TB
// 1 << (10*4)
PB
// 1 << (10*5)
EB
// 1 << (10*6)
ZB
// 7 << (10*5)
1024
1048576
1073741824
...
不使用 iota 的代码,对于代码洁癖者来说,简直就是一坨,不可接受。
而 Go 语言的发明者,恰恰具有代码洁癖,而且还是深度洁癖。Go 语言设计初衷之一:追求简洁优雅。
iota 源码在 Go 语言代码库中,只有一句定义语句,
// Untyped int.
iota 是一个预声明的标识符,它的值是 0。 在 const 常量声明中,作为当前 const 代码块中的整数序数。
FirstItem
SecondItem
ThirdItem
编译上述代码:
# 使用 -N -l 编译参数用于禁止内联和优化,防止编译器优化和简化代码,弄乱次序。这样便于阅读汇编代码。
go tool compile -N -l main.go
# 导出汇编代码:
go tool objdump main.o
TEXT
22
22.
SB
gofile
Users
wangzebin
test
go
MOVQ $
0x0
SP
// 对应源码 println(FirstItem)
CALL
0x33b
R_CALL
runtime
printint
0x1
// 对应源码 println(SecondItem)
0x357
0x2
// 对应源码 println(ThirdItem)
0x373
编译之后,对应的常量 FirstItem、SecondItem 和 ThirdItem,分别替换为$0x0、$0x1 和 $0x2。
这说明:Go代码中定义的常量,在编译时期就会被替换为对应的常量(所以根本不会有地址)。当然 iota,也不可避免地在编译时期,按照一定的规则,被替换为对应的常量。
所以,Go 语言源码库中是不会有 iota 源码了,它的魔法在编译时期就已经施展完毕。也就是说,解释 iota 的代码包含在 go 这个命令和其调用的组件中。
如果你要阅读它的源码,准确的说,阅读处理 iota 关键字的源码,需要到 Go 工具源码库中寻找,而不是 Go 核心源码库。
使用 iota,虽然可以书写简洁优雅的代码,但对于不熟悉规则的人来讲,又带来的很多不必要的麻烦和误解。
对于引入 iota,到底好是不好,每个人都有自己的评价。实际上,有些不常用的写法,甚至有些卖弄编写技巧的的写法,并不是设计者的初衷。
大多数情况下,我们还是使用最简单最明确的写法,iota 只是提供了一种选择而已。一个工具使用的好坏,取决于使用它的人,而不是工具本身。
以下是 iota 编译规则:
iota 依赖于 const 关键字,每次新的 const 关键字出现时,都会让 iota 初始化为0。
// a=0
// b=0
// c=1
iota 按行递增加 1。
// b=1
// c=2
同一 const 块出现多个 iota,只会按照行数计数,不会重新计数。
空行在编译时期首先会被删除,所以空行不计数。
占位 “_”,它不是空行,会进行计数,起到跳值作用。
// _=1
开头插队会进行计数。
// i=3.14
// j=1
// k=2
// l=3
中间插队会进行计数。
// i=0
// j=3.14
一行多个iota,分别计数。
// i=0,j=0
// k=1,l=1
对于20221016中文网给出的每日一题来说,这样的iota用法完成就是
一坨大便
,就如同大家痛恨的某cpp书中
a++--++
之类的代码,如果用Go的思想去看,你的代码写成这样只说明了两点:1. 你的代码出问题了 2. 你人出问题了。
下面赋值正确的是:
nil
interface
error
参考答案及解析:BD。
这道题考的知识点是 nil。nil 只能赋值给指针、chan、func、interface、map 或 slice 类型的变量。强调下 D 选项的 error 类型,它是一种内置接口类型,看它的源码就知道,所以 D 是对的。
type
Error
以下代码打印出来什么内容
People
Show
Student
stu
live
return
"AAAAAAA"
"BBBBBBB"
interface 在使用的过程中,共有两种表现形式:一种为空接口(empty interface),
另一种为非空接口(non-empty interface)
空接口 eface 结构,由两个属性构成,一个是类型信息 _type,一个是数据信息。
eface
// 空接口
_type
// 类型信息
data
unsafe
Pointer
// 指向数据的指针
face 结构中最重要的是 itab 结构(结构如下),每一个 itab 都占 32 字节的空间。
iface
tab
itab
data unsafe
itab 里面包含了 interface 的一些关键信息,比如 method 的具体实现。
inter
interfacetype
// 接口自身的元信息
// 具体类型的元信息
hash
int32
// _type 里也有一个同样的 hash,此处多放一个是为了方便运行接口断言
fun
uintptr
// 函数指针,指向具体类型所实现的方法
所以,People 拥有一个 Show 方法,属于非空接口,People 的内部定义是一个iface结构体。
stu 是一个指向 nil 的空指针,但是最后return stu 会触发匿名变量 People = stu 值拷贝动作,所以最后live()放回给上层的是一个People insterface{}类型,也就是一个iface struct{}类型。 stu 为 nil,只是iface中的 data 为 nil 而已。 但是iface struct{}本身并不为 nil.
以下代码输出什么
"encoding/json"
"time"
time
Time
Date
2020
12
20
UTC
json
Marshal
Printf
"%s"
"2020-12-20T00:00:00Z"
Person
Name
`json:"name"`
Hobby
`json:"hobby"`
person
"polarisxu"
hobby
"Golang"
要想输出 {“name”:“polarisxu”,“hobby”:"Golang”},一般我们会这么做:将 Person 的字段导出,同时设置上 tag。
但如果不想导出 Person 的字段可以通过实现 Marshaler 来做到。
p Person
MarshalJSON
`{"name":"`
`
","
":"
`+p.hobby+`
"
time.Time 是一个没有导出任何字段的结构体类型,因此它肯定实现了 Marshaler 接口。
// contains filtered or unexported fields
// MarshalJSON implements the json.Marshaler interface.
// The time is a quoted string in RFC 3339 format, with sub-second precision added if present.
t Time
Year
<
||
>=
10000
// RFC 3339 is clear that years are 4 digits exactly.
// See golang.org/issue/4556#c15 for more discussion.
errors
New
"Time.MarshalJSON: year outside of range [0,9999]"
make
RFC3339Nano
'"'
AppendFormat
正是因为内嵌,t 的方法集包括了 time.Time 的方法集,所以,t 自动实现了 Marshaler 接口。
其实这道题的情况,在日常工作中还真有可能遇到。所以,当你内嵌某个类型时,特别这个类型不是你自己定义的,需要留意这种情况。
一般解决这个问题的方法有两种:1)不内嵌;2)重新实现 MarshalJSON 方法。
然而这道题无法重新实现 MarshalJSON 方法,因为结构体类型是匿名的。只能通过不内嵌来得到正确的结果。
Marshal traverses the value v recursively. If an encountered value implements the Marshaler interface and is not a nil pointer, Marshal calls its MarshalJSON method to produce JSON. If no MarshalJSON method is present but the value implements encoding.TextMarshaler instead, Marshal calls its MarshalText method and encodes the result as a JSON string. The nil pointer exception is not strictly necessary but mimics a similar, necessary exception in the behavior of UnmarshalJSON.
如果值实现了 json.Marshaler 接口并且不是 nil 指针,则 Marshal 函数会调用其 MarshalJSON 方法以生成 JSON。如果不存在 MarshalJSON 方法,但该值实现 encoding.TextMarshaler 接口,则 Marshal 函数调用其 MarshalText 方法并将结果编码为 JSON 字符串。
可见,json.Marshal 函数优先调用 MarshalJSON,然后是 MarshalText,如果都没有,才会走正常的类型编码逻辑。
GetValue
switch
case
"int"
"string"
"interface"
default
"unknown"
编译失败。
类型断言,类型断言的语法形如:i.(type),其中 i 是接口,type 是固定关键字,需要注意的是,只有接口类型才可以使用类型断言。
执行下面的代码会发生什么
ch
chan
1000
for
++
<-
ok
"close"
"a: "
close
"ok"
Sleep
Second
panic: send on closed channel
给一个 nil channel 发送数据,造成永远阻塞
从一个 nil channel 接收数据,造成永远阻塞
给一个已经关闭的 channel 发送数据,引起 panic
从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值
无缓冲的channel是同步的,而有缓冲的channel是非同步的
本题中,因为 main 在开辟完两个 goroutine 之后,立刻关闭了 ch, 结果就是 panic
以下代码有什么问题
"sync"
wg
sync
WaitGroup
Add
defer
Done
Wait
输出结果不唯一,代码存在风险, 所有 go 语句未必都能执行到。
go语句执行太快了,导致 wg.Add(1) 还没有执行 main 函数就执行完毕了。wg.Add 的位置放错了。
下面这段代码输出什么?
"mike"
m 是一个 map,值是 nil。
从 nil map 中取值不会报错,而是返回相应的零值,这里值是 int 类型,因此返回 0。
nil map 可以取值,但不能直接赋值
hello
num
18
可变参数函数会将参数变成切片
"%T \n"
值拷贝的形式传入,底层数组是一样的
可变参数是切片,切片是引用,所以func内赋值会带出来。
sum
vals
// "0"
// "3"
// "10"
在上面的代码中,调用者隐式的创建一个数组,并将原始参数复制到数组中,再把数组的一个切片作为参数传给被调用函数。
如果原始参数已经是切片类型,只需在最后一个参数后加上省略符。
下面的代码功能与上个例子中最后一条语句相同。
values
虽然在可变参数函数内部,…int 型参数的行为看起来很像切片类型,但实际上,可变参数函数和以切片作为参数的函数是不同的。
"%T\n"
// "func(...int)"
// "func([]int)"
以下代码能否通过编译?
worker
work
w worker
能通过编译
接口也是类型,自然也将它作为嵌入类型。
这个类型默认就实现了这个接口
实例化 person 时,没有给 worker 指定值,因此 person 中的 worker 是 nil,调用它的话会报错
worker worker
sort包中:
这是用于排序的
Interface
// Len is the number of elements in the collection.
Len
// Less reports whether the element with
// index i should sort before the element with index j.
Less
bool
// Swap swaps the elements with indexes i and j.
Swap
这是用于倒序的
reverse
// This embedded Interface permits Reverse to use the methods of
// another Interface implementation.
使用
Reverse
data Interface
实例化 reverse 时,直接通过传递的 Interface 实例赋值给 reverse 的内嵌接口,然后 reverse 类型可以有选择的重新实现内嵌的 Interface 的方法。
通过实例化的 w 调用 work 方法会报错,需要给 person 中的 worker 实例化,也就是需要一个实现了 worker 接口的类型实例。
student
s student
"I am "
", I am learning"
然后这样实例化 person:
截取操作符还可以有第三个参数,形如 [i,j,k],第三个参数 k 用来限制新切片的容量,但不能超过原数组(切片)的底层数组大小。
截取获得的切片的长度和容量分别是:j-i、k-i。
"%+d %+d"
参考答案及解析:A。
%d表示输出十进制数字,+表示输出数值的符号。这里不表示取反。
ShowA
"showA"
ShowB
"showB"
Teacher
"teacher showB"
teacher showB。
在嵌套结构体中,People 称为内部类型,Teacher 称为外部类型;
通过嵌套,内部类型的属性、方法,可以为外部类型所有,就好像是外部类型自己的一样。
此外,外部类型还可以定义自己的属性和方法,甚至可以定义与内部相同的方法,这样内部类型的方法就会被“屏蔽”。
这个例子中的 ShowB() 就是同名方法。
下面代码段输出什么?
28
// 1.
// 2.
// 3.
29
参考答案及解析:29 29 28。变量 person 是一个指针变量 。
1.person.age 此时是将 28 当做 defer 函数的参数,会把 28 缓存在栈中,等到最后执行该 defer 语句的时候取出,即输出 28;
2.defer 缓存的是结构体 Person{28} 的地址,最终 Person{28} 的 age 被重新赋值为 29,所以 defer 语句最后执行的时候,依靠缓存的地址取出的 age 便是 29,即输出 29;
3.很简单,闭包引用,输出 29;
又由于 defer 的执行顺序为先进后出,即 3 2 1,所以输出 29 29 28。
A、B、C、D 哪些选项有语法错误?
//A
//B
//C
//D
BD。
函数参数为 interface{} 时可以接收任何类型的参数,包括用户自定义类型等,即使是接收指针类型也用 interface{},而不是使用 *interface{}。
永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。
s1
s2
[1 2 4]
golang 中切片底层的数据结构是数组
当使用 s1[1:] 获得切片 s2,和 s1 共享同一个底层数组,这会导致 s2[1] = 4 语句影响 s1。
append 操作会导致底层数组扩容,生成新的数组,因此追加数据后的 s2 不会影响 s1。
下面这段代码输出什么?为什么?
"s is nil"
"s is not nil"
p People
"p is nil"
"p is not nil"
参考答案及解析:s is nil 和 p is not nil。
分配给变量 p 的值明明是 nil,然而 p 却不是 nil。当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。上面的代码,给变量 p 赋值之后,p 的动态值是 nil,但是动态类型却是 *Student,是一个 nil 指针,所以相等条件不成立。
Direction
North Direction
East
South
West
d Direction
String
"North"
"East"
"South"
"West"
参考答案及解析:South。
根据 iota 的用法推断出 South 的值是 2;
如果类型定义了 String() 方法,当使用 fmt.Printf()、fmt.Print() 和 fmt.Println() 会自动使用 String() 方法,实现字符串的打印。
println(South)
依旧会打印2.
当改为指针接收者时不会调用String()
原因:当对象是指针时,不用管接收者是指针接收者还是非指针接收者;当对象不是指针时,无法调用指针接收者的方法。
下面代码输出什么?
Math
"foo"
编译报错 cannot assign to struct field m[“foo”].x in map。
错误原因:对于类似 X = Y的赋值操作,必须知道 X 的地址,才能够将 Y 的值赋给 X,但 go 中的 map 的 value 本身是不可寻址的。
究其原因,因为Go的map是通过散列表来实现的,说得更具体一点,就是通过数组和链表组合实现的。并且Go的map也可以做到动态扩容,当进行扩容之后,map的value那块空间地址就会产生变化,所以无法对map的value进行寻址。
使用临时变量
tmp
修改数据结构
"%#v"
// %#v 格式化输出详细信息
以下代码的输出结果:
wg sync
foo
bar
select
"default"
对于 select 语句,在进入该语句时,会按源码的顺序对每一个 case 子句进行求值:这个求值只针对发送或接收操作的额外表达式。将代码改为:
val
// case foo <- <-bar:
此时,正常输出:default
所以上述语句的理解就是:getVal()、<-input 和 getch() 等类似的操作会执行,导致死锁
getVal
"in first case"
"in second case"
"The val:"
"getVal, i="
无论 select 最终选择了哪个 case,getVal() 都会按照源码顺序执行:getVal(1) 和 getVal(2),必然输出:getVal, i= 1 | getVal, i= 2
为什么每次都是输出一半数据,然后死锁?
talk
msg
sleep
Sprintf
"%s %d"
Duration
Millisecond
fanIn
input1
input2
"A"
"B"
"%q\n"
每次进入以下 select 语句时:<-input1 和 <-input2 都会执行,相应的值是:A x 和 B x(其中 x 是 0-5)。但每次 select 只会选择其中一个 case 执行,所以 <-input1 和 <-input2 的结果,必然有一个被丢弃了,也就是不会被写入 ch 中。因此,一共只会输出 5 次,另外 5 次结果丢掉了。
而 main 中循环 10 次,只获得 5 次结果,所以输出 5 次后,报死锁。
// ch 是一个 chan int;
// getVal() 返回 int
// input 是 chan int
// getch() 返回 chan int
input
getch
有内存泄露(传递给 time.After 的时间参数越大,泄露会越厉害)
PS: 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
After
30
Now
Unix
每次执行select都会执行一次time.After(30*time.Second)获取一个chan。
下面这段代码能否正常结束?
range
参考答案及解析:不会出现死循环,能正常结束。
循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数。
in
print
+=
recover
参考答案及解析:7。
// 入栈
// f为nil
// f赋值为function
// r = n+1
// return r
1.
2.
执行
即,
引发
panic
3.
被异常捕获,执行r
4.
在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:
13
"r = "
"a = "
range 表达式是副本参与循环,就是说例子中参与循环的是 a 的副本,而
不是真正的 a
如果想要 r 和 a 一样输出,修复办法:
使用 *[5]int 作为 range 表达式,其副本依旧是一个指向原数组 a 的指针,因此后续所有循环中均是 &a 指向的原数组亲自参与的,因此 v 能从 &a 指向的原数组中取出 a 修改后的值。
GO中无论做什么都是把原值copy一份,原值是指针就copy指针,原值是值类型就copy值类型。
change
slice
知识点:可变函数、append()操作。
Go 提供的语法糖…,可以将 slice 传进可变函数,不会创建新的切片。第一次调用 change() 时,append() 操作使切片底层数组发生了扩容,原 slice 的底层数组不会改变; 第二次调用change() 函数时,使用了操作符[i,j]获得一个新的切片,假定为 slice1,
它的底层数组和原切片底层数组是重合的,不过 slice1 的长度、容量分别是 2、5,所以在 change() 函数中对 slice1 底层数组的修改会影响到原切片。
下面这段代码输出结果正确吗?
Foo
"C"
value
输出:
参考答案及解析:s2 的输出结果错误。
s2 的输出是 &{C} &{C} &{C},在前面题目我们提到过,for range 使用短变量声明(:=)的形式迭代变量时,变量 i、value 在每次循环体中都会被重用,而不是重新声明。所以 s2 每次填充的都是临时变量 value 的地址,而在最后一次循环中,value 被赋值为{c}。因此,s2 输出的时候显示出了三个 &{c}。
可行的解决办法如下:
// 赋值地址:b[i]是val的地址
不是地址赋值可以正常使用:
下面代码里的 counter 的输出值?
21
23
counter
delete
"counter is "
for range map 是无序的,如果第一次循环到 A,则输出 3;否则输出 2,并且对m的修改会生效,但不影响循环次数.
下面代码输出正确的是?
"Z"
"s: %v \n"
多重赋值分为两个步骤,有先后顺序:
计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式;
赋值;
所以,会先计算 s[i-1],等号右边是两个表达式是常量,所以赋值运算等同于 i, s[0] = 2, “Z”。
如何确认两个 map 是否相等?
map 深度相等的条件:
都为 nil
非空、长度相等,指向同一个 map 实体对象
相应的 key 指向的 value “深度”相等
直接将使用 map1 == map2 是错误的。这种写法只能比较 map 是否为 nil。
// 不能通过编译
//fmt.Println(m == n)
或者直接利用反射:
"relflect"
DeepEqual对于map是否相等也是从以上三点判断的。
Map
v1
IsNil
!=
v2
false
true
MapKeys
val1
MapIndex
val2
IsValid
deepValueEqual
visited
// 以下有额外内存分配吗?
在 Go 中,接口被实现为一对指针:指向有关类型信息的指针和指向值的指针。可以简单的表示为:
// 类型
// 值
其中 tab 是指向类型信息的指针;data 是指向值的指针。因此,一般来说接口意味着必须在堆中动态分配该值。
然而,
Go 1.15 发行说明
在 runtime 部分中提到了一个有趣的改进:
Converting a small integer value into an interface value no longer causes allocation.
意思是说,将小整数转换为接口值不再需要进行内存分配。小整数是指 0 到 255 之间的数。
函数中进行了 100 次 int 到 interface 的转换.
smallint
Convert
smallint_test
"testing"
"test/smallint"
BenchmarkConvert
testing
result
go1.14 和 go1.15
version
version go1
14.7
darwin
amd64
bench
benchmem
goos
goarch
pkg
569830
1966
ns
op
2592
101
allocs
PASS
647s
15
1859451
655
1792
178s
go 1.15,但把12改为256
551546
2049
502s
证明了上面提到的优化点。
Go 中定义了一个特殊的静态数组,该数组由 256 个整数组成(0 到 255)。当必须分配内存以将整数存储在堆上,并将其转换为接口的一部分时,它首先检查是否它可以只返回指向数组中适当元素的指针。这种经常使用的值的静态分配,是一种很常见的优化手段,例如,Python 对小整数执行类似的操作。
以下代码能否编译?
"Aceld"
"student"
"LDB"
编译失败,cannot assign to struct field list[“student”].Name in map
map[string]Student 的 value 是一个 Student 结构值,所以当list[“student”] = student,是一个值拷贝过程。而list[“student”]则是一个值引用。那么值引用的特点是只读。所以对list[“student”].Name = "LDB"的修改是不允许的。
将 map 的类型的 value 由 Student 值,改成 Student 指针。
实际上每次修改的都是指针所指向的 Student 空间,指针本身是常指针,不能修改,只读属性,但是指向的 Student 是可以随便修改的,而且这里并不需要值拷贝。只是一个指针的赋值。
优惠劵
关注
点赞
觉得还不错?
一键收藏
打赏
知道了
评论
iota 在下一行增长,而不是立即取得它的引用。
复制链接
扫一扫
专栏目录
1-6年级数学
易错
集锦
.pdf
05-15
Go
语言
开发
技巧
100例(二)
做兴趣使然的Hero
11-05
487
第二期~
参与评论
您还未登录,请先
登录
后发表或查看评论
Wik-
12-07
171
1. fallthrough关键字
2. 简式变量声明仅能在函数内部使用
100例(一)
10-15
848
今天给大家带来的这一档文章呢,主要是总结一下自己Coding过程中遇到的
问题
以及平时读一些博客的所得,因为做gopher也有了一段时间了,相比Java,有些
的出现想要利用搜索引擎排查出来可能不是那么的迅速,所以在这里以文章的形式总结出来也方便各位初出茅庐的gopher们能够顺利的解决所遇到的
,并能够习得一些小
100例(十二)
02-14
1424
反射是一种在运行时检查、修改和调用对象类型和值的能力。在Go
中,
08-08
之后再算p+1的时候,dp[n]因为已经算过了,会直接返回,但实际上它是1到n的值而不是你想要的2到n+1的值)include “xxx.h”的意思是;每次先讨
javascript
知识
实例小结
主要介绍了javascript
,结合实例形式总结分析了javascript 对象属性、继承常见
与注意事项,需要的朋友可以参考下
css+div的一些
09-25
最近在
网站,经常用到css+div,在不断的错误中,发现一些规律.
高三政治
12-04
Golang
| Leetcode
题解之第37题解数独
weixin_66442839的博客
04-19
649
go版本1.16.5 运行项目出现undefined: math.MaxInt报错
尚墨1111的博客
04-24
215
math.maxInt报错
题解之第46题全排列
04-23
436
中栈和堆对数据密集型应用程序性能的影响
禅与代码的艺术
1164
基础7-并发编程
最新发布
m0_61973596的博客
04-25
838
基础-并发编程
并发实战——日志收集系统(五) 基于go-ini包读取日志收集服务的配置文件
qq_73924465的博客
04-20
583
对读取配置文件就讲到这里了。下一篇文章我们就开始探寻如何初始化Kafka和tail的服务,以及介绍什么是etcd,以及它在项目中所起到的作用,下篇见!
Go之map详解
cocotww的博客
1129
数组元素为“桶”,每个桶中包含高8位的hash和相应的8个key-value,高8位hash用来快速找到目标key,其次是8个key,8个value(key和value分开存储,是为了防止key存储空间大于value时,value会自动占用key大小的空间,这样做可以减少空间的浪费),最后是指向溢出桶的指针(解决哈希冲突)。如果遇到哈希冲突,即不同key产生的hash值一样,如此就需要额外进行key值的比较,这就要求存储的key值是可以比较相等的,如果是扩容迁移,旧桶元素可能迁移到X部,也可能迁移到Y部。
调用阿里通义千问的接口
hjx_dou的专栏
268
有的项目可能需要用到直接外部调用阿里通义千问的接口。官方只有python和java的sdk。其实
简单的调用也非常简单,就是一个封装的http调用。
并发赋值的安全性
c0210g的博客
249
结构体中有多个字段,此时struct赋值时,并不是原子操作,各个字段之间独立,在并发操作时可能会出现异常.
在linux中配置RAID的配置
06-10
其中软件RAID是最为常见的一种方式,我们在这里主要介绍软件RAID的配置
1.了解RAID的基本概念和原理:在配置RAID之前,需要了解RAID的基本概念和原理,包括RAID的级别和RAID的实现原理等。
2.选择...
“相关推荐”对你有帮助么?
非常没帮助
没帮助
一般
有帮助
非常有帮助
提交
CSDN认证博客专家
CSDN认证企业博客
码龄4年
暂无认证
234
原创
4万+
周排名
1万+
总排名
44万+
访问
等级
3145
积分
1445
粉丝
515
获赞
89
2199
私信
热门文章
python3.8,3.9,3.10,3.11特性比较
20993
将python项目(django/flask)打包成exe和安装包
15878
python操作word——python-docx和python-docx-template模块
12437
各类配置文件格式简介(ini,yaml,conf,xml...)和nb的Viper
10116
[python爬虫]爬取电影,电视剧
7450
分类专栏
AI
4篇
django
12篇
flask
6篇
python
67篇
elasticsearch
51篇
git
3篇
C/C++
1篇
计算机原理
8篇
kubernetes(k8s)
微服务
11篇
kafka
2篇
linux
9篇
mongodb
gin
爬虫
7篇
Lua
算法
nginx
docker
计算机网络
10篇
其他
redis
MySQL
密码学
5篇
网络安全
7天系列——Go实战从0到1
shell
最新评论
GitHub项目监控——gitpython&go-git5操作git
m0_62196601:
这里推荐一个不需要开发即可监控三方GitHub仓库更新的方法
https://blog.csdn.net/m0_62196601/article/details/137919537
感谢读者的分享,这里推荐一个自动监控github变化并发送通知的方法,不需要开发,开箱即用
https://editor.csdn.net/md/?articleId=137919537
O4AS_ONE:
# 问题2:打开网页出现TemplateDoesNotExist 错误
解决方法:把项目中的模板文件templates拷贝到dist下的manage文件夹,刷新页面即可。
放进去了,但是还是说找不到
Generalzy:
理论上字节码应该不会用到。所以缺省应该是不影响程序运行的。
qt的包我没尝试打包过。非常抱歉可能帮不上忙。
最新文章
langChain学习笔记(待续)
AI入门系列——数据分析(待续)
光速入门spark(待续)
2024年
2023年
89篇
2022年
107篇
2021年
35篇
被折叠的 
 条评论
为什么被折叠?
到【灌水乐园】发言
查看更多评论
添加红包
祝福语
请填写红包祝福语或标题
红包数量
红包个数最小为10个
红包总金额
红包金额最低5元
余额支付
当前余额
3.43
前往充值 >
需支付:
10.00
取消
确定
下一步
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝
规则
hope_wisdom
发出的红包
打赏作者
倍感荣幸文章对您有帮助
¥1
¥2
¥4
¥6
¥10
¥20
扫码支付:
获取中
扫码支付
您的余额不足,请更换扫码支付或
充值
实付
使用余额支付
点击重新获取
钱包余额
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。
余额充值