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("触发异常")
}

在线运行

答: 输出内容为:

打印后
打印中
打印前
panic: 触发异常

解析:考察对 defer 的理解,defer 函数属延迟执行,延迟到调用者函数执行 return 命令前被执行。多个 defer 之间按 LIFO 先进后出顺序执行。

故考题中,在 Panic 触发时结束函数运行,在 return 前先依次打印:打印后、打印中、打印前 。最后由 runtime 运行时抛出打印 panic 异常信息。

需要注意的是,函数的 return value 不是原子操作,而是在编译器中分解为两部分:返回值赋值 和 return 。而 defer 刚好被插入到末尾的 return 前执行。故可以在 derfer 函数中修改返回值。如下示例:

package main

import (
"fmt"
) func main() {
fmt.Println(doubleScore(0)) //0
fmt.Println(doubleScore(20.0)) //40
fmt.Println(doubleScore(50.0)) //50
} func doubleScore(source float32) (score float32) {
defer func() {
if score < 1 || score >= 100 {
//将影响返回值
score = source
}
}()
score = source * 2
return //或者
//return source * 2
}

在线运行

该实例可以在 defer 中修改返回值 score 的值。具体参见官方文档

2、以下代码有什么问题,说明原因

package main

import (
"fmt"
) type student struct {
Name string
Age int
} func pase_student() map[string]*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
}
return m
} func main() {
students := pase_student()
for k, v := range students {
fmt.Printf("key=%s,value=%v \n", k, v)
}
}

在线运行

答:输出的均是相同的值:&{wang 22}

解析:因为 for 遍历时,变量 stu 指针不变,每次遍历仅进行 struct 值拷贝,故 m[stu.Name]=&stu 实际上一致指向同一个指针,最终该指针的值为遍历的最后一个 struct 的值拷贝。形同如下代码:

var stu student
for _, stu = range stus {
m[stu.Name] = &stu
}

修正方案,取数组中原始值的指针:

for i, _ := range stus {
stu:=stus[i]
m[stu.Name] = &stu
}

3、下面的代码会输出什么,并说明原因

func main() {
runtime.GOMAXPROCS(1)
wg := sync.WaitGroup{}
wg.Add(20)
for i := 0; i < 10; i++ {
go func() {
fmt.Println("i: ", i)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println("i: ", i)
wg.Done()
}(i)
}
wg.Wait()
}

在线运行

答: 将随机输出数字,但前面一个循环中并不会输出所有值。

解析:实际上第一行是否设置 CPU 为 1 都不会影响后续代码。两个 for 循环内部 go func 调用参数i的方式是不同的,导致结果完全不同。这也是新手容易遇到的坑。

第一个 go func 中 i 是外部 for 的一个变量,地址不变化。遍历完成后,最终 i=10。故 go func 执行时,i的值始终是 10(10次遍历很快完成)。

第二个 go func 中 i 是函数参数,与外部 for 中的i完全是两个变量。尾部(i)将发生值拷贝,go func 内部指向值拷贝地址。

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()
}

在线运行

答: 将输出:

showA
showB

解析:Go 中没有继承! 没有继承!没有继承!是叫组合!组合!组合!

这里 People 是匿名组合 People。被组合的类型 People 所包含的方法虽然升级成了外部类型 Teacher 这个组合类型的方法,但他们的方法(ShowA())调用时接受者并没有发生变化。

这里仍然是 People。毕竟这个 People 类型并不知道自己会被什么类型组合,当然也就无法调用方法时去使用未知的组合者 Teacher 类型的功能。

因此这里执行 t.ShowA() 时,在执行 ShowB() 时该函数的接受者是 People,而非 Teacher。具体参见官方文档

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)
}
}

在线运行

答: 有可能触发异常,是随机事件。

解析:单个 chan 如果无缓冲时,将会阻塞。但结合 select 可以在多个 chan 间等待执行。有三点原则:

  • select 中只要有一个 case 能 return,则立刻执行。
  • 当如果同一时间有多个 case 均能 return 则伪随机方式抽取任意一个执行。
  • 如果没有一个 case 能 return 则可以执行“default”块。

此考题中的两个 case 中的两个 chan 均能 return,则会随机执行某个 case 块。故在执行程序时,有可能执行第二个 case,触发异常。具体参见官方文档

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
}

在线运行

答:输出结果为:

10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4

解析:在解题前需要明确两个概念: + defer 是在函数末尾的 return 前执行,先进后执行,具体见问题1。 + 函数调用时 int 参数发生值拷贝。

不管代码顺序如何,defer calc func 中参数 b 必须先计算,故会在运行到第三行时,执行 calc("10",a,b) 输出:10 1 2 3 得到值 3,将 cal("1",1,3) 存放到延后执执行函数队列中。

执行到第五行时,现行计算 calc("20", a, b) 即 calc("20", 0, 2) 输出:20 0 2 2 得到值 2,将 cal("2",0,2) 存放到延后执行函数队列中。

执行到末尾行,按队列先进后出原则依次执行:cal("2",0,2)cal("1",1,3) ,依次输出:2 0 2 21 1 3 4 。

7、请写出以下输入内容

func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}

在线运行

答: 将输出:[0 0 0 0 0 1 2 3]

解析:make 可用于初始化数组,第二个可选参数表示数组的长度。数组是不可变的。

当执行 make([]int,5) 时返回的是一个含义默认值(int的默认值为0)的数组:[0,0,0,0,0]。而 append 函数是便是在一个数组或 slice 后面追加新的元素,并返回一个新的数组或 slice。

这里 append(s,1,2,3) 是在数组s的继承上追加三个新元素:1、2、3,故返回的新数组为 [0 0 0 0 0 1 2 3]

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
}

在线运行

答: 在执行 Get 方法时可能被 panic

解析:虽然有使用 sync.Mutex 做写锁,但是 map 是并发读写不安全的。map 属于引用类型,并发读写时多个协程见是通过指针访问同一个地址,即访问共享变量,此时同时读写资源存在竞争关系。会报错误信息:“fatal error: concurrent map read and map write”。

可以在在线运行中执行,复现该问题。那么如何改善呢? 当然 Go1.9 新版本中将提供并发安全的 map。首先需要了解两种锁的不同:

  1. sync.Mutex 互斥锁
  2. sync.RWMutex 读写锁,基于互斥锁的实现,可以加多个读锁或者一个写锁。

利用读写锁可实现对 map 的安全访问,在线运行改进版 。利用 RWutex 进行读锁。

type RWMutex
func (rw *RWMutex) Lock()
func (rw *RWMutex) RLock()
func (rw *RWMutex) RLocker() Locker
func (rw *RWMutex) RUnlock()
func (rw *RWMutex) Unlock()

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
}

在线运行

答: 内部迭代出现阻塞。ch 初始化的容量默认为 1,如果外部不及时读取,则内部迭代是阻塞状态。

解析:chan 在使用 make 初始化时可附带一个可选参数来设置缓冲区。默认无缓冲,题目中便初始化的是无缓冲区的 chan,这样只有写入的元素直到被读取后才能继续写入,不然就一直阻塞。

设置缓冲区大小后,写入数据时可连续写入到缓冲区中,直到缓冲区被占满。从 chan 中接收一次便可从缓冲区中释放一次。可以理解为 chan 是可以设置吞吐量的处理池。

纠正一下:

内部迭代出现阻塞。ch 初始化的容量默认为1,如果外部不及时读取,则内部迭代是阻塞状态。 这句话是错误的

ch := make(chan interface{}) 和 ch := make(chan interface{},1)是不一样的

无缓冲的 不仅仅是只能向 ch 通道放 一个值 而是一直要有人接收,那么 ch <- elem 才会继续下去,要不然就一直阻塞着,也就是说有接收者才去放,没有接收者就阻塞。

而缓冲为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))
}

在线运行

答: 编译失败,值类型 Student{} 未实现接口 People 的方法,不能定义为 People 类型。

解析:考题中的 func (stu *Stduent) Speak(think string) (talk string) 是表示结构类型 *Student 的指针有提供该方法,但该方法并不属于结构类型 Student 的方法。因为 struct 是值类型。

修改方法:

  • 定义为指针 go var peo People = &Stduent{}
  • 方法定义在值类型上,指针类型本身是包含值类型的方法。 go func (stu Stduent) Speak(think string) (talk string) { //... }

摘自:

https://gocn.io/article/395

https://yushuangqi.com/blog/2017/golang-mian-shi-ti-da-an-yujie-xi.html

Go 面试题(附答案解析)的更多相关文章

  1. 2020年大厂Java面试前复习的正确姿势(800+面试题附答案解析)

    前言 个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事.所以,劝各位不要因为面试失败而灰心. 丧失斗志.也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! 本篇分享的面试题内容 ...

  2. Java容器--2021面试题系列教程(附答案解析)--大白话解读--JavaPub版本

    Java容器--2021面试题系列教程(附答案解析)--大白话解读--JavaPub版本 前言 序言 再高大上的框架,也需要扎实的基础才能玩转,高频面试问题更是基础中的高频实战要点. 适合阅读人群 J ...

  3. 找工作的你不容错过的45个PHP面试题附答案(下篇)

    找工作的你不容错过的45个PHP面试题附答案(上篇) Q28:你将如何使用PHP创建Singleton类? /** * Singleton class * */ final class UserFac ...

  4. PHP面试题及答案解析(8)—PHP综合应用题

    1.写出下列服务的用途和默认端口. ftp.ssh.http.telnet.https ftp:File Transfer Protocol,文件传输协议,是应用层的协议,它基于传输层,为用户服务,它 ...

  5. PHP面试题及答案解析(7)—Linux系统命令

    1.请解释下列10个shell命令的用途.top.ps.mv.find.df.cat.chmod.chgrp.grep.wc top:该命令提供了实时对系统处理器状态的监控,它能够实时显示系统中各个进 ...

  6. PHP面试题及答案解析(5)—数据结构与算法

    1.使对象可以像数组一样进行foreach循环,要求属性必须是私有.(Iterator模式的PHP5实现,写一类实现Iterator接口) <?php class Test implements ...

  7. PHP面试题及答案解析(6)—PHP网络编程

    1.禁用COOKIE后SEESION还能用吗? 可以,COOKIE和SESSION都是用来实现会话机制的,由于http协议是无状态的,所以要想跟踪一个用户在同一个网站之间不同页面的状态,需要有这么一个 ...

  8. PHP面试题及答案解析(4)—PHP核心技术

    1.写出一个能创建多级目录的PHP函数. <?php /** * 创建多级目录 * @param $path string 要创建的目录 * @param $mode int 创建目录的模式,在 ...

  9. PHP面试题及答案解析(3)—MySQL数据库

    1.mysql_num_rows()和mysql_affected_rows()的区别. mysql_num_rows()和mysql_affected_rows(),这两个函数都作用于 mysql_ ...

  10. PHP面试题及答案解析(2)—PHP面向对象

    1. 写出 php 的 public.protected.private 三种访问控制模式的区别. public:公有,任何地方都可以访问protected:继承,只能在本类或子类中访问,在其它地方不 ...

随机推荐

  1. Python实现网页截图(PyQT5)

    方案说明 功能要求:实现网页加载后将页面截取成长图片涉及模块:PyQT5 PIL逻辑说明: 1:完成窗口设置,利用PyQT5 QWebEngineView加载网页地址,待网页加载完成后,调用check ...

  2. C# 解决VS2008在win7找不到输入序列号的地方

    1.VS2008在Windows7 打开维护界面看不到可以输序列号的地方. 因为微软把他隐藏了. 2.我们可以借用工具把他显示出来 下载地址:http://www.zlsoft.com/techbbs ...

  3. (AC自动机)C - 病毒侵袭持续中

    题目链接:https://cn.vjudge.net/contest/280743#problem/C 题目大意:中文题 具体思路:首先取ascii码0-130是肯定不行的了,会超时.然后就开始简化, ...

  4. linux c中select使用方法

    1.select函数作为定时器使用    it_value.tv_sec = 0;    it_value.tv_usec = 100000:    select(1,NULL,NULL,NULL,& ...

  5. HTML5 drawImage性能问题

    最近做游戏发现,为了节省HTTP请求,把很多组动画放到了一张图片上,也就是传说中的雪碧图,但是用drawImage从图片上截取一部分绘制在canvas上时会非常慢. 我的图片尺寸为5000*7000 ...

  6. Ubuntu环境下TensorBoard 可视化 不显示数据问题 No scalar data was found...(作者亲测有效)(转)

    TensorBoard:Tensorflow自带的可视化工具.利用TensorBoard进行图表可视化时遇到了图表不显示的问题. 环境:Ubuntu系统 运行代码,得到TensorFlow的事件文件l ...

  7. Windows caffe VGG人脸识别

    caffe自带的例子有mnist和cifar10,cifar10和mnist的运行方式类型,下好图片数据文件后,训练例子中的模型,然后测试模型,也可以自己用图片进行预测分类(自己图片最好是cifar1 ...

  8. ECS Navicat for MySQL远程连接报10038的错误

    解决问题时,建议先在阿里云设置好数据库访问的白名单,把自己的IP地址填进去 问题现象 Navicat for MySQL访问远程mysql数据库,出现报错,显示“2003- Can't connect ...

  9. .NetCore Linux中安装Grafana界面及配置InfluxDB相关设置

    前面的文章已经安装好了InfluxDB 安装 wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1. ...

  10. iOS 一句话获取日期和星期几

    UILabel *dateLabel = [[UILabel alloc]initWithFrame:CGRectMake(, , self.view.frame.size.width, )]; da ...