1. 代码考题
1.1.1. 写出下面代码输出的内容
package main
import (
"fmt"
)
func main() {
defer_call()
}
func defer_call() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印后") }()
panic("触发异常")
}
考点:defer执行顺序
解答: defer 是后进先出。 协程遇到panic时,遍历本协程的defer链表,并执行defer。在执行defer过程中,遇到recover则停止panic,返回recover处继续往下执行。如果没有遇到recover,遍历完本协程的defer链表后,向stderr抛出panic信息。从执行顺序上来看,实际上是按照先进后出的顺序执行defer
输出:
打印后
打印中
打印前
panic: 触发异常
注意: 请用独立终端运行,排查某些IDE对stderr和stdout处理问题导致输出顺序不一致
参考: defer
1.1.2. 以下代码有什么问题,说明原因
type student struct {
Name string
Age int
}
func pase_student() {
m := make(map[string]*student)
stus := []student{
{Name: "zhou", Age: 24},
{Name: "li", Age: 23},
{Name: "wang", Age: 22},
}
for _, stu := range stus {
m[stu.Name] = &stu
}
}
考点: foreach
解答: 这样的写法初学者经常会遇到的,很危险! 与Java的foreach一样,都是使用副本的方式。所以m[stu.Name]=&stu实际上一致指向同一个指针, 最终该指针的值为遍历的最后一个struct的值拷贝。
1.1.3. 下面代码会输出什么,并说明原因
func main() {
runtime.GOMAXPROCS(1)
wg := sync.WaitGroup{}
wg.Add(20)
for i := 0; i < 10; i++ {
go func() {
fmt.Println("A: ", i)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println("B: ", i)
wg.Done()
}(i)
}
wg.Wait()
}
考点:go执行的随机性和闭包
解答: 谁也不知道执行后打印的顺序是什么样的,所以只能说是随机数字。
这题有坑,其实就是 第一行 "runtime.GOMAXPROCS(1)" 这行代码
如果没有这行
其中A:
输出完全随机,取决于goroutine执行时i的值是多少;
而B:
一定输出为0~9,但顺序不定。
第一个go func中i是外部for的一个变量,地址不变化,但是值都在改变。
第二个go func中i是函数参数,与外部for中的i完全是两个变量。 尾部(i)将发生值拷贝,go func内部指向值拷贝地址。
所以在使用goroutine在处理闭包的时候,避免发生类似第一个go func中的问题。
如果有这行, 那么执行的P就一个, 代码会先执行第一个for,里面的go并不会执行(会进入调度的队列里)
然后第一个for执行完成(此时 i = 10),主协程还没有阻塞, 那么这时候会继续执行第二个for循环,
第二个for循环执行结束,执行到 wg.Wait() 主协程触发阻塞,这时候cpu调度切换,开始执行go的协程
这时候就看队列里谁在前了, 所以 A会一直输出10 B会从0输出到9 但是顺序不一定,这才是这题最终答案
1.1.4. 下面代码会输出什么?
type People struct{}
func (p *People) ShowA() {
fmt.Println("showA")
p.ShowB()
}
func (p *People) ShowB() {
fmt.Println("showB")
}
type Teacher struct {
People
}
func (t *Teacher) ShowB() {
fmt.Println("teacher showB")
}
func main() {
t := Teacher{}
t.ShowA()
}
考点:go的组合继承
解答: 这是Golang的组合模式,可以实现OOP的继承。 被组合的类型People所包含的方法虽然升级成了外部类型Teacher这个组合类型的方法(一定要是匿名字段),但它们的方法(ShowA())调用时接受者并没有发生变化。 此时People类型并不知道自己会被什么类型组合,当然也就无法调用方法时去使用未知的组合者Teacher类型的功能。
输出:
showA
showB
1.1.5. 下面代码会触发异常吗?请详细说明?
func main() {
runtime.GOMAXPROCS(1)
int_chan := make(chan int, 1)
string_chan := make(chan string, 1)
int_chan <- 1
string_chan <- "hello"
select {
case value := <-int_chan:
fmt.Println(value)
case value := <-string_chan:
panic(value)
}
}
考点:select随机性
解答: select会随机选择一个可用通用做收发操作。 所以代码是有肯触发异常,也有可能不会。 单个chan如果无缓冲时,将会阻塞。但结合 select可以在多个chan间等待执行。
有三点原则:
- select 中只要有一个case能return,则立刻执行。
- 当如果同一时间有多个case均能return则伪随机方式抽取任意一个执行。
- 如果没有一个case能return则可以执行”default”块。
1.1.6. 下面代码输出什么?
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
a := 1
b := 2
defer calc("1", a, calc("10", a, b))
a = 0
defer calc("2", a, calc("20", a, b))
b = 1
}
考点:defer执行顺序
解答: 这道题类似第1题 需要注意到defer执行顺序和值传递 index:1肯定是最后执行的,但是index:1的第三个参数是一个函数,所以最先被调用calc(“10”,1,2)==>10,1,2,3 执行index:2时,与之前一样,需要先调用calc(“20”,0,2)==>20,0,2,2 执行到b=1时候开始调用,index:2==>calc(“2”,0,2)==>2,0,2,2 最后执行index:1==>calc(“1”,1,3)==>1,1,3,4
输出:
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4
1.1.7. 请写出以下输出内容
func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
考点:make默认值和append
解答: make初始化是由默认值的哦,此处默认值为0
输出:
[0 0 0 0 0 1 2 3]
1.1.8. 下面的代码有什么问题?
type UserAges struct {
ages map[string]int
sync.Mutex
}
func (ua *UserAges) Add(name string, age int) {
ua.Lock()
defer ua.Unlock()
ua.ages[name] = age
}
func (ua *UserAges) Get(name string) int {
if age, ok := ua.ages[name]; ok {
return age
}
return -1
}
考点:map线程安全
解答: 可能会出现fatal error: concurrent map read and map write
.
1.1.9. 下面的迭代会有什么问题?
func (set *threadSafeSet) Iter() <-chan interface{} {
ch := make(chan interface{})
go func() {
set.RLock()
for elem := range set.s {
ch <- elem
}
close(ch)
set.RUnlock()
}()
return ch
}
考点:chan缓存池
解答: 看到这道题,我也在猜想出题者的意图在哪里。 chan?sync.RWMutex?go?chan缓存池?迭代? 所以只能再读一次题目,就从迭代入手看看。 既然是迭代就会要求set.s全部可以遍历一次。但是chan是为缓存的,那就代表这写入一次就会阻塞。 我们把代码恢复为可以运行的方式,看看效果
package main
import (
"sync"
"fmt"
)
//下面的迭代会有什么问题?
type threadSafeSet struct {
sync.RWMutex
s []interface{}
}
func (set *threadSafeSet) Iter() <-chan interface{} {
// ch := make(chan interface{}) // 解除注释看看!
ch := make(chan interface{},len(set.s))
go func() {
set.RLock()
for elem,value := range set.s {
ch <- elem
println("Iter:",elem,value)
}
close(ch)
set.RUnlock()
}()
return ch
}
func main() {
th:=threadSafeSet{
s:[]interface{}{"1","2"},
}
v:=<-th.Iter()
fmt.Sprintf("%s%v","ch",v)
}
1.1.10. 以下代码能编译过去吗?为什么?
package main
import (
"fmt"
)
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{}
think := "bitch"
fmt.Println(peo.Speak(think))
}
考点:golang的方法集
解答: 编译不通过! 做错了!?说明你对golang的方法集还有一些疑问。 一句话:golang的方法集仅仅影响接口实现和方法表达式转化,与通过实例或者指针调用方法无关。
cannot use composite literal (type Stduent) as type People in assignment: Stduent does not implement People (Speak method has pointer receiver)
1.1.11. 以下代码打印出来什么内容,说出为什么?
package main
import (
"fmt"
)
type People interface {
Show()
}
type Student struct{}
func (stu *Student) Show() {
}
func live() People {
var stu *Student
return stu
}
func main() {
if live() == nil {
fmt.Println("AAAAAAA")
} else {
fmt.Println("BBBBBBB")
}
}
考点:interface内部结构
解答: 很经典的题! 这个考点是很多人忽略的interface内部结构。 go中的接口分为两种一种是空的接口类似这样:
var in interface{}
另一种如题目:
type People interface {
Show()
}
他们的底层结构如下:
type eface struct { //空接口
_type *_type //类型信息
data unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type iface struct { //带有方法的接口
tab *itab //存储type信息还有结构实现方法的集合
data unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type _type struct {
size uintptr //类型大小
ptrdata uintptr //前缀持有所有指针的内存大小
hash uint32 //数据hash值
tflag tflag
align uint8 //对齐
fieldalign uint8 //嵌入结构体时的对齐
kind uint8 //kind 有些枚举值kind等于0是无效的
alg *typeAlg //函数指针数组,类型实现的所有方法
gcdata *byte
str nameOff
ptrToThis typeOff
}
type itab struct {
inter *interfacetype //接口类型
_type *_type //结构类型
link *itab
bad int32
inhash int32
fun [1]uintptr //可变大小 方法集合
}
可以看出iface比eface 中间多了一层itab结构。 itab 存储_type信息和[]fun方法集,从上面的结构我们就可得出,因为data指向了nil 并不代表interface 是nil, 所以返回值并不为空,这里的fun(方法集)定义了接口的接收规则,在编译的过程中需要验证是否实现接口
结果:
BBBBBBB
1.1.12. 是否可以编译通过?如果通过,输出什么?
func main() {
i := GetValue()
switch i.(type) {
case int:
println("int")
case string:
println("string")
case interface{}:
println("interface")
default:
println("unknown")
}
}
func GetValue() int {
return 1
}
考点:type
编译失败,因为type只能使用在interface
1.1.13. 下面函数有什么问题?
func funcMui(x,y int)(sum int,error){
return x+y,nil
}
考点:函数返回值命名
在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名。 如果返回值有有多个返回值必须加上括号; 如果只有一个返回值并且有命名也需要加上括号; 此处函数第一个返回值有sum名称,第二个未命名,所以错误。
1.1.14. 是否可以编译通过,如果通过,输出什么?
package main
func main() {
println(DeferFunc1(1))
println(DeferFunc2(1))
println(DeferFunc3(1))
}
func DeferFunc1(i int) (t int) {
t = i
defer func() {
t += 3
}()
return t
}
func DeferFunc2(i int) int {
t := i
defer func() {
t += 3
}()
return t
}
func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}
考点:defer和函数返回值
需要明确一点是defer需要在函数结束前执行。 函数返回值名字会在函数起始处被初始化为对应类型的零值并且作用域为整个函数
DeferFunc1有函数返回值t作用域为整个函数,在return之前defer会被执行,所以t会被修改,返回4;
DeferFunc2函数中t的作用域为函数,返回1;
DeferFunc3返回3
1.1.15. 是否可以编译通过,如果通过,输出什么?
func main() {
list := new([]int)
list = append(list, 1)
fmt.Println(list)
}
考点:new
list:=make([]int,0)
1.1.16. 是否可以编译通过,如果通过,输出什么?
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5}
s1 = append(s1, s2)
fmt.Println(s1)
}
考点:append
append切片时候别漏了’…’
两数组合并必须加 ...
如果是一般的加一个元素,可以直接append加
1.1.17. 是否可以编译通过,如果通过,输出什么?
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"}}
if sm1 == sm2 {
fmt.Println("sm1 == sm2")
}
}
考点:结构体比较
进行结构体比较时候,只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。
sn3:= struct {
name string
age int
}{age:11,name:"qq"}
sn3与sn1就不是相同的结构体了,不能比较。 还有一点需要注意的是结构体是相同的,但是结构体属性中有不可以比较的类型,如map,slice。 如果该结构属性都是可以比较的,那么就可以使用“==”进行比较操作。
可以使用reflect.DeepEqual进行比较
if reflect.DeepEqual(sn1, sm) {
fmt.Println("sn1 ==sm")
}else {
fmt.Println("sn1 !=sm")
}
所以编译不通过:
invalid operation: sm1 == sm2
1.1.18. 是否可以编译通过,如果通过,输出什么?
func Foo(x interface{}) {
if x == nil {
fmt.Println("empty interface")
return
}
fmt.Println("non-empty interface")
}
func main() {
var x *int = nil
Foo(x)
}
考点:interface内部结构
non-empty interface
1.1.19. 是否可以编译通过,如果通过,输出什么?
func GetValue(m map[int]string, id int) (string, bool) {
if _, exist := m[id]; exist {
return "存在数据", true
}
return nil, false
}
func main() {
intmap:=map[int]string{
1:"a",
2:"bb",
3:"ccc",
}
v,err:=GetValue(intmap,3)
fmt.Println(v,err)
}
考点:函数返回值类型
nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。但是如果不特别指定的话,Go 语言不能识别类型,所以会报错。通常编译的时候不会报错,但是运行是时候会报:cannot use nil as type string in return argument
.
1.1.20. 是否可以编译通过,如果通过,输出什么?
const (
x = iota
y
z = "zz"
k
p = iota
)
func main() {
fmt.Println(x,y,z,k,p)
}
1.1.21. 编译执行下面代码会出现什么?
package main
var(
size :=1024
max_size = size*2
)
func main() {
println(size,max_size)
}
考点:变量简短模式
变量简短模式限制:
- 定义变量同时显式初始化
- 不能提供数据类型
- 只能在函数内部使用
结果:
syntax error: unexpected :=
1.1.22. 下面函数有什么问题?
package main
const cl = 100
var bl = 123
func main() {
println(&bl,bl)
println(&cl,cl)
}
考点:常量
常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,
cannot take the address of cl
1.1.23. 编译执行下面代码会出现什么?
package main
func main() {
for i:=0;i<10 ;i++ {
loop:
println(i)
}
goto loop
}
考点:goto
goto不能跳转到其他函数或者内层代码
goto loop jumps into block starting at
1.1.24. 编译执行下面代码会出现什么?
package main
import "fmt"
func main() {
type MyInt1 int
type MyInt2 = int
var i int =9
var i1 MyInt1 = i
var i2 MyInt2 = i
fmt.Println(i1,i2)
}
考点:Go 1.9 新特性 Type Alias
基于一个类型创建一个新类型,称之为defintion;基于一个类型创建一个别名,称之为alias。 MyInt1为称之为defintion,虽然底层类型为int类型,但是不能直接赋值,需要强转; MyInt2称之为alias,可以直接赋值。
结果:
cannot use i (type int) as type MyInt1 in assignment
1.1.25. 编译执行下面代码会出现什么?
package main
import "fmt"
type User struct {
}
type MyUser1 User
type MyUser2 = User
func (i MyUser1) m1(){
fmt.Println("MyUser1.m1")
}
func (i User) m2(){
fmt.Println("User.m2")
}
func main() {
var i1 MyUser1
var i2 MyUser2
i1.m1()
i2.m2()
}
考点:Go 1.9 新特性 Type Alias
因为MyUser2完全等价于User,所以具有其所有的方法,并且其中一个新增了方法,另外一个也会有。 但是
i1.m2()
是不能执行的,因为MyUser1没有定义该方法。 结果:
MyUser1.m1
User.m2
假如 是这样呢?
type MyUser1 int
type MyUser2 = int
func (i MyUser1) m1(){
fmt.Println("MyUser1.m1")
}
func (i User) m2(){
fmt.Println("User.m2")
}
func main() {
var i1 MyUser1
var i2 MyUser2
i1.m1()
i2.m2()
答案 是会报错的:
i2.m2 undefined (type int has no field or method m2)
因为int是内置类型,上面的 User
结构体是自定义的
1.1.26. 编译执行下面代码会出现什么?
package main
import "fmt"
type T1 struct {
}
func (t T1) m1(){
fmt.Println("T1.m1")
}
type T2 = T1
type MyStruct struct {
T1
T2
}
func main() {
my:=MyStruct{}
my.m1()
}
考点:Go 1.9 新特性 Type Alias
是不能正常编译的,异常:
ambiguous selector my.m1
结果不限于方法,字段也也一样;也不限于type alias,type defintion也是一样的,只要有重复的方法、字段,就会有这种提示,因为不知道该选择哪个。 改为:
my.T1.m1()
my.T2.m1()
type alias的定义,本质上是一样的类型,只是起了一个别名,源类型怎么用,别名类型也怎么用,保留源类型的所有方法、字段等。
1.1.27. 编译执行下面代码会出现什么?
package main
import (
"errors"
"fmt"
)
var ErrDidNotWork = errors.New("did not work")
func DoTheThing(reallyDoIt bool) (err error) {
if reallyDoIt {
result, err := tryTheThing()
if err != nil || result != "it worked" {
err = ErrDidNotWork
}
}
return err
}
func tryTheThing() (string,error) {
return "",ErrDidNotWork
}
func main() {
fmt.Println(DoTheThing(true))
fmt.Println(DoTheThing(false))
}
考点:变量作用域
因为 if 语句块内的 err 变量会遮罩函数作用域内的 err 变量,结果:
<nil>
<nil>
改为:
func DoTheThing(reallyDoIt bool) (err error) {
var result string
if reallyDoIt {
result, err = tryTheThing()
if err != nil || result != "it worked" {
err = ErrDidNotWork
}
}
return err
}
1.1.28. 编译执行下面代码会出现什么?
package main
func test() []func() {
var funs []func()
for i:=0;i<2 ;i++ {
funs = append(funs, func() {
println(&i,i)
})
}
return funs
}
func main(){
funs:=test()
for _,f:=range funs{
f()
}
}
考点:闭包延迟求值
for循环复用局部变量i,每一次放入匿名函数的应用都是想一个变量。 结果:
0xc042046000 2
0xc042046000 2
如果想不一样可以改为:
func test() []func() {
var funs []func()
for i:=0;i<2 ;i++ {
x:=i
funs = append(funs, func() {
println(&x,x)
})
}
return funs
}
首先得注意 闭包和匿名函数的区别! 另外,看执行顺序,会发现test先执行,但是test()
里的闭包并没有执行,只有在range funs
的时候 闭包才执行...那时候里面的i
最后是啥值,结果就是啥值...
1.1.29. 编译执行下面代码会出现什么?
package main
func test(x int) (func(),func()) {
return func() {
println(x)
x+=10
}, func() {
println(x)
}
}
func main() {
a,b:=test(100)
a()
b()
}
考点:闭包引用相同变量*
结果:
100
110
1.1.30. 编译执行下面代码会出现什么?
package main
import (
"fmt"
"reflect"
)
func main() {
defer func() {
if err:=recover();err!=nil{
fmt.Println(err)
}else {
fmt.Println("fatal")
}
}()
defer func() {
panic("defer panic")
}()
panic("panic")
}
考点:panic仅有最后一个可以被revover捕获
触发panic("panic")
后顺序执行defer,但是defer中还有一个panic,所以覆盖了之前的panic("panic")
defer panic
1.1.31. 执行下面代码会发生什么?
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 1000)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
}()
go func() {
for {
a, ok := <-ch
if !ok {
fmt.Println("close")
return
}
fmt.Println("a: ", a)
}
}()
close(ch)
fmt.Println("ok")
time.Sleep(time.Second * 100)
}
考点:channel
往已经关闭的channel写入数据会panic的。
结果:
panic: send on closed channel
1.1.32. 执行下面代码会发生什么?
import "fmt"
type ConfigOne struct {
Daemon string
}
func (c *ConfigOne) String() string {
return fmt.Sprintf("print: %v", c)
}
func main() {
c := &ConfigOne{}
c.String()
}
考点:fmt.Sprintf
如果类型实现String(),%v和%v格式将使用String()的值。因此,对该类型的String()函数内的类型使用%v会导致无限递归。 编译报错:
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
1.1.33. 输出什么?
package main
import (
"fmt"
)
func main() {
fmt.Println(len("你好bj!"))
}
考点:编码长度
输出9
1.1.34. ✭编译并运行如下代码会发生什么?
package main
import "fmt"
type Test struct {
Name string
}
var list map[string]Test
func main() {
list = make(map[string]Test)
name := Test{"xiaoming"}
list["name"] = name
list["name"].Name = "Hello"
fmt.Println(list["name"])
}
考点:map
编程报错cannot assign to struct field list["name"].Name in map
。 因为list[“name”]不是一个普通的指针值,map的value本身是不可寻址的,因为map中的值会在内存中移动,并且旧的指针地址在map改变时会变得无效。 定义的是var list map[string]Test,注意哦Test不是指针,而且map我们都知道是可以自动扩容的,那么原来的存储name的Test可能在地址A,但是如果map扩容了地址A就不是原来的Test了,所以go就不允许我们写数据。你改为var list map[string]*Test试试看。
1.1.35. ABCD中哪一行存在错误?
type S struct {
}
func f(x interface{}) {
}
func g(x *interface{}) {
}
func main() {
s := S{}
p := &s
f(s) //A
g(s) //B
f(p) //C
g(p) //D
}
考点:interface
看到这道题需要第一时间想到的是Golang是强类型语言,interface是所有golang类型的父类,类似Java的Object。 函数中func f(x interface{})
的interface{}
可以支持传入golang的任何类型,包括指针,但是函数func g(x *interface{})
只能接受*interface{}
.
1.1.36. 编译并运行如下代码会发生什么?
package main
import (
"sync"
)
const N = 10
var wg = &sync.WaitGroup{}
func main() {
for i := 0; i < N; i++ {
go func(i int) {
wg.Add(1)
println(i)
defer wg.Done()
}(i)
}
wg.Wait()
}
考点:WaitGroup
这是使用WaitGroup经常犯下的错误!请各位同学多次运行就会发现输出都会不同甚至又出现报错的问题。 这是因为go
执行太快了,导致wg.Add(1)
还没有执行main函数就执行完毕了。 改为如下试试
for i := 0; i < N; i++ {
wg.Add(1)
go func(i int) {
println(i)
defer wg.Done()
}(i)
}
wg.Wait()
1.1.37. 以下会输出什么?
func main() {
s := make([]int,3,8)
a := s[:9]
fmt.Println(s,a)
}
考点: make的第三个参数 make第一个参数是类型,第二个参数长度,第三个是预留分配空间 在长度为3的范围内正常赋值,若想使用到长度为8的空间,需要重新切片!但是切片长度不能超过8
所以会报错:
panic: runtime error: slice bounds out of range
1.1.38. 以下会输出什么?
var f = func(i int) {
print("x")
}
func main() {
f := func(i int) {
print(i)
if i > 0 {
f(i - 1)
}
}
f(10)
}
考点: 匿名函数的调用
匿名函数是不能自己调用自己的, f(i - 1)
其实是上面var
定义的 f
所以:
10 x
1.1.39. 以下会输出什么?
chan_n := make(chan bool)
chan_c := make(chan bool, 1)
done := make(chan struct{})
go func() {
for i := 1; i < 11; i += 2 {
<-chan_c
fmt.Print(i)
fmt.Print(i + 1)
chan_n <- true
}
}()
go func() {
char_seq := []string{"A","B","C","D","E","F","G","H","I","J","K"}
for i := 0; i < 10; i += 2 {
<-chan_n
fmt.Print(char_seq[i])
fmt.Print(char_seq[i+1])
chan_c <- true
}
done <- struct{}{}
}()
chan_c <- true
<-done
考点: chan的用法 两goroutine里的chan都没有值,一直在等,到倒数第二行,chan_c 赋值了,这时上面那个 goroutine开始执行,然后给 chan_n 赋值,此时 chan_c 已经没有值了,又在等待,但是第二个 goroutine的chan_n有了值,然后第二个开始执行,然后后面又给 chan_c 赋值.. 如此循环..打印所有
所以:
12AB34CD56EF78GH910IJ
1.1.40. 以下会输出什么?
const N = 10
func ttest() {
m := make(map[int]int)
wg := &sync.WaitGroup{}
wg.Add(N)
for i:=0;i<N;i++ {
go func() {
defer wg.Done()
m[rand.Int()] = rand.Int()
}()
}
wg.Wait()
fmt.Println(len(m))
}
考点: map并发读写的问题 并发访问map并不安全,会出现未定义行为,会导致程序退出,如果希望在多协程里并发访问map,必须提供某种同步机制,通常可以使用读写锁 sync.RWMutex实现 对map的并发访问控制!
所以:
fatal error: concurrent map writes
1.1.41. 以下会输出什么?
func a() {
ch1 := make(chan int,1)
ch1 <- 1
close(ch1)
fmt.Println(<-ch1)
}
func b() {
ch2 := make(chan int,1)
ch2 <- 1
close(ch2)
for v := range ch2 {
fmt.Println(v)
}
}
func c() {
ch3 := make(chan int,1)
ch3 <- 1
close(ch3)
ch3 <- 2
}
考点: 信道 信道关闭后,无法继续写的,但是可以读
所以: 1 1 panic: send on closed channel
1.1.42. 以下会输出什么?
var T int64 = a()
func init() {
fmt.Println("init in main.go")
}
func a() int64 {
fmt.Println("calling a()")
return 2
}
func main() {
fmt.Println("calling main")
}
考点: Golang加载过程
在一个go文件中, 初始化顺序规则:
(1) 引入的包
(2) 当前包中的变量常量
(3) 当前包的init
(4) main函数
注意:
当前go源文件中, 每一个被Import的包, 按其在源文件中出现顺序初始化。
如果当前包有多个init在不同的源文件中, 则按源文件名以字典序从小到大排序,小的先被执行到, 同一包且同一源文件中的init,则按其出现在文件中的先后顺序依次初始化; 当前包的package level变量常量也遵循这个规则; 其实准确来说,应是按提交给编译器的源文件名顺序为准,只是在提交编译器之前, go命令行工具对源文件名按字典序排序了。
init只可以由go runtine自已调用, 我们在代码中不可以显示调用,也不可以被引用,如赋给a function variable。
包A 引入包B , 包B又引入包C, 则包的初始化顺序为: C -> B -> A
引入包,必须避免死循环,如 A 引 B , B引C, C引A.
一个包被其它多个包引入,如A -> B ->C 和 H -> I -> C , C被其它包引了2次, 但是注意包C只被初始化一次。
另一个大原则, 被依赖的总是先被初始化,当然呀。
main包总是被最后一个初始化,这很好理解,因为它总是依赖别的包。
所以: calling a() init in main.go calling main
1.1.43. 下面对add函数调用正确的是()?
func add(args ...int) int {
sum := 0
for _, arg := range args {
sum += arg
}
return sum
}
A. add(1, 2)
B. add(1, 3, 7)
C. add([]int{1, 2})
D. add([]int{1, 3, 7}...)
考点: 传递变长参数
如果函数的最后一个参数是采用 ...type 的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为 0,这样的函数称为变参函数。
这个函数接受一个类似某个类型的 slice 的参数,不过需要...
将其打散
答案: A B D
1.2. 以上部分资源来自网络
参考: Go 夜读
参考: v2ex