10个面试必备问题 *

Toptal提供了最好的围棋工程师可以回答的基本问题. 在我们社区的推动下,我们鼓励专家提交问题并提供反馈.

现在就雇佣一名顶尖的工程师
Toptal标志是顶级自由软件开发人员的专属网络吗, designers, 金融专家, 产品经理, 和世界上的项目经理. 顶级公司雇佣Toptal自由职业者来完成他们最重要的项目.

面试问题

1.

如何交换两个值? 提供几个例子.

查看答案

交换两个值就像这样简单:

A b = b A

要交换三个值,我们可以这样写:

A b c = b c A

Go中的交换操作保证没有副作用. 要赋值的值保证在开始实际赋值之前存储在临时变量中, 所以赋值顺序无关紧要. 以下操作的结果: a := 1; b := 2; a, b, a = b, a, b is still a = 2 and b = 1,而没有改变值的风险 a 到新的重新赋值的值. 这在许多算法实现中是有用的.

例如,一个将整型切片反转的函数:

函数reverse(s []int) {
        for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
                S [i], S [j] = S [j], S [i]
        }
}

函数main() {
	A:= []int{1,2,3}
	反向(一个)
	fmt.Println ()
	//输出:[3 2 1]
}
2.

如何复制切片、映射和接口?

查看答案

您可以使用内置的 copy() function:

A:= []int{1, 2}
B:= []int{3,4}
检查:= a
复制(a, b)
fmt.Println(a, b, check)
//输出:[3 4][3 4][3 4]

Here, the check 变量用于保存对原始片描述的引用,以确保它确实被复制.

在下一个例子中, 另一方面, 操作不复制切片内容, 只有切片描述:

A:= []int{1, 2}
B:= []int{3,4}
检查:= a
a = b
fmt.Println(a, b, check)
//输出:[3 4][3 4][1 2]

通过遍历键来复制映射. 是的,不幸的是,这是在Go中复制map的最简单方法:

a:= map[string]bool{" a ": true, "B": true}
B:= make(map[string]bool)
对于键,value:= range a {
	B [key] = value
}

下面的例子只复制了地图的描述:

a:= map[string]bool{" a ": true, "B": true}
b:= map[string]bool{"C": true, "D": true}
检查:= a
a = b
fmt.Println(a, b, check)
//输出:map[C:true D:true] map[C:true D:true] map[A:true B:true]

Go中没有内置的方法来复制接口. No, the reflect.DeepCopy() 功能不存在.

3.

如何比较两个结构体? 两个接口怎么样?? 提供的例子.

查看答案

可以比较两个结构体 == 操作符,就像处理其他简单类型一样. 只要确保它们不包含任何切片, maps, 或功能, 在这种情况下,代码将不会被编译.

类型Foo结构{
	A int
	B string
	C接口{}
}
a:= Foo{a: 1, B: " 1 ", C: " 2 "}
b:= Foo{A: 1, b: " 1 ", C: " 2 "}

Println (a == b)
//输出:true

Bar结构{
	A []int
}
a:= Bar{a: []int{1}}
b:= Bar{A: []int{1}}

Println (a == b)
//输出:无效操作:a == b (struct包含[]int不能比较)

可以比较两个接口 == 操作符,只要底层类型“简单”且相同即可. 否则代码将在运行时panic:

Var接口{}
变量b接口{}

a = 10
b = 10
Println (a == b)
//输出:true

A = []int{1}
B = []int{2}
Println (a == b)
//输出:panic:运行时错误:比较不可比较的类型[]int

包含映射、切片(但不包含函数)的结构体和接口都可以与 reflect.DeepEqual () function:

Var接口{}
变量b接口{}

A = []int{1}
B = []int{1}
println(反映.DeepEqual (a, b))
//输出:true

a = map[string]string{" a ": "B"}
b = map[string]string{"A": " b "}
println(反映.DeepEqual (a, b))
//输出:true

Temp:= func() {}
a = temp
b = temp
println(反映.DeepEqual (a, b))
//输出:false

类中有一些很好的辅助函数来比较字节片 bytes package: bytes.Equal(), bytes.Compare(), and bytes.EqualFold (). 后者用于比较文本字符串而不考虑大小写,这比 reflect.DeepEqual ().

申请加入Toptal的发展网络

并享受可靠、稳定、远程 自由职业围棋工程师

申请成为自由职业者
4.

下面的代码片段有什么问题?

type Orange struct {
	int数量
}

函数(0 *橙色)增加(n int) {
	o.数量+= n
}

function(0 *橙色)reduce (n int) {
	o.数量-= n
}

function (o *Orange) String() String {
	返回fmt.Sprintf(“% v”,o.Quantity)
}

函数main() {
	var橙橙橙
	orange.增加(10)
	orange.(5)减少
	fmt.Println(橙色)
}

提供适当的代码解决方案.

查看答案

这是一个棘手的问题,因为你可能认为这与成员变量有关 Quantity 设置错误,但实际上,它将被设置为预期的5. 真正的问题很容易被忽视,那就是 String() 方法,该方法实现 fmt.斯金格() 对象时,将不会调用 orange 正被印上 fmt.Println() 函数,因为方法 String() 不是定义在一个值上,而是定义在一个指针上:

var橙橙橙
orange.增加(10)
orange.(5)减少
fmt.Println(橙色)
//输出:{5}

orange := &Orange{}
orange.增加(10)
orange.(5)减少
fmt.Println(橙色)
//输出:5

这是一个微妙的问题,但解决方法很简单. 你需要重新定义 String() 方法用于值而不是指针,在这种情况下,它将同时用于指针和值:

function (o Orange) String() String {
	返回fmt.Sprintf(“% v”,o.Quantity)
}
5.

如何在Go中实现堆栈和队列? 解释并提供代码示例.

查看答案

你可以自己使用切片来实现堆栈或队列:

类型栈[]int
func (s栈)Empty() bool{返回len(s) == 0}
func (s *栈)推动(v int) {(*) = append ((*), v)}
function (s *Stack) Pop() int {
	V:= (*s)[len(*s)-1]
	(*s) = (*s)[:len(*s)-1]
	return v
}

type Queue []int
func (q Queue) Empty() bool{返回len(q) == 0}
func (q *Queue) Enqueue(v int) {(*q) = append((*q), v)}
function (q *Queue) Dequeue() int {
	v := (*q)[0]
	(*q) = (*q)[1:len(*q)]
	return v
}

函数main() {
	s:=栈{}
	s.Push(1)
	s.Push(2)
	fmt.Println(s.Pop())
	fmt.Println(s.Pop())
	fmt.Println(s.Empty())
	/ /输出:
	// 2
	// 1
	// true

	q:=队列{}
	q.排队(1)
	q.排队(2)
	fmt.Println(q.出列())
	fmt.Println(q.出列())
	fmt.Println(q.Empty())
	/ /输出:
	// 1
	// 2
	// true
}

上面的队列实现是正确的,但它不是最优的. 有更好但更长的实现,比如 this one.

有时,您更喜欢Go标准库 容器/列表 为了实现它们的简洁性, genericity, 和额外列表数据结构相关的操作:

Stack:= list.New()
stack.推迟(1)
stack.推迟(2)
fmt.Println(堆栈.Remove(堆栈.Back()))
fmt.Println(堆栈.Remove(堆栈.Back()))
fmt.Println(堆栈.Len() == 0)
/ /输出:
// 2
// 1
// true

队列:= list.New()
queue.推迟(1)
queue.推迟(2)
fmt.Println(队列.Remove(队列.Front()))
fmt.Println(队列.Remove(队列.Front()))
fmt.Println(队列.Len() == 0)
/ /输出:
// 1
// 2
// true

Although, 通常不鼓励使用它们,因为它们的性能较慢, 与切片迭代模式相比. 让我们比较一下下面的两个例子:

//遍历列表并打印其内容.
对于e:= queue.Front(); e != nil; e = e.Next() {
    fmt.Println(e.Value)
}
对于_,e:= range queue {
    fmt.Println (e)
}

“一定要用一片.”, 戴夫·切尼

实现队列的另一种可能性是使用缓冲通道, 但这绝不是个好主意, because:

  1. 缓冲区大小在通道创建时确定,不能增加.
  2. 如果不从队列中检索下一个队列元素,就不可能看到它.
  3. 存在死锁的风险:“新手有时会尝试在单个程序中使用缓冲通道作为队列, 被它们令人愉悦的简单语法所吸引, 但这是一个错误. 通道与日常调度密切相关, 并且不需要另一个从通道接收的程序, 一个发送者——也许是整个程序——有可能永远被封锁. 如果您只需要一个简单的队列,那就使用切片创建一个.”, Brian Kernighan.
6.

下面的小程序可能有什么问题?

函数main() {
	扫描器:= bufio.NewScanner(字符串.NewReader(“一个
two
three
four
`))
	var (
		文本字符串
		n    int
	)
	对于扫描仪.Scan() {
		n++
		文本+= FMT.Sprintf(“% d. %s\n", n,扫描器.Text())
	}
	fmt.打印(文本)

	/ /输出:
	// 1. One
	// 2. Two
	// 3. Three
	// 4. Four
}

程序对缓冲区中的行进行编号,并使用 文本/扫描仪 逐行读取输入. 有什么问题吗?

查看答案

First, 在将字符串输出到标准输出之前,没有必要收集字符串中的输入. 这个例子有点做作.

第二,字符串文本没有被修改 += 操作符,它为每一行重新创建. 这是字符串和 []byte 切片-字符串在Go中是不可修改的. 如果需要修改字符串,请使用 []byte slice.

下面是一个提供的小程序,以更好的方式编写:

函数main() {
	扫描器:= bufio.NewScanner(字符串.NewReader(“一个
two
three
four
`))
	var (
		文本[]字节
		n    int
	)
	对于扫描仪.Scan() {
		n++
		Text = append(Text, FMT.Sprintf(“% d. %s\n", n,扫描器.Text())...)
	}
	os.Stdout.写(文本)
	// 1. One
	// 2. Two
	// 3. Three
	// 4. Four
}

这就是两者存在的意义 bytes and strings packages.

7.

如果需要按固定顺序显示散列,该怎么做?

查看答案

你需要对哈希的键进行排序.

水果:= map[string]int{
	“橙子”:100年,
	“苹果”:200年,
	“香蕉”:300年,
}

//将键放入切片并对其进行排序.
Var keys []string
对于键:=范围水果{
	键= append(键,键)
}
sort.字符串(键)

//根据排序的切片显示键.
对于_,键:=范围键{
	fmt.Printf("%s:%v\n",键,水果[键])
}
/ /输出:
/ /苹果:200
/ /香蕉:300
/ /橙子:100
8.

有什么区别?, if any, 在下面两个片声明中, 哪一个更可取?

Var a []int

and

A:= []int{}
查看答案

如果片未被使用,第一个声明不会分配内存, 因此,这种声明方法是首选的.

9.

两者都需要吗? GOPATH and GOROOT 变量,以及为什么?

查看答案

大多数时候,你并不需要两者都需要. 你只需要 GOPATH 变量集指向Go包树.

GOROOT 指向Go语言主目录的根目录, 但它很可能已经设置为当前Go语言安装的目录. 很容易检查是否如此 go env command:

$ go env
…
GOROOT = " / home / zabb /去”
…

有必要设置 GOROOT 如果在同一系统上有多个Go语言版本,或者Go语言是作为二进制包从互联网下载的,或者是从另一个系统传输的,则变量.

10.

为什么你更喜欢用空的 struct{}? 提供一些很好的使用空的例子 struct{}.

查看答案

当您想要节省一些内存时,可以使用空结构体. 空结构体的值不占用任何内存.

A:= struct{}{}
println(不安全.Sizeof (a))
//输出:0

这种节省通常是微不足道的,并且取决于切片或映射的大小. 不过,空结构体更重要的用途是向读者显示您根本不需要值. 在大多数情况下,它的目的主要是提供信息. 下面是一些有用的例子:

  • 在实现数据集时:
Set:= make(map[string]struct{})
对于_,value:= range []string{"apple", "orange", "apple"} {
   Set [value] = struct{}{}
}
fmt.Println(套)
//输出:map[orange:{} apple:{}]
  • With the seen 哈希,就像遍历图一样:
查看:= make(map[string]struct{})
对于_,ok:= seen[v]; !ok {
    //第一次访问顶点.
    [v] = struct{}{}
}
  • 在构建对象时, 只对一组方法感兴趣,没有中间数据, 或者不打算保留对象状态时. 在下面的例子中,调用同一个对象(情况#1)或两个不同对象(情况#2)的方法没有区别:
类型灯结构{}

function (l灯)On() {
        println(“”)

}
func (l灯)灭(){
        println(“关闭”)
}

函数main() {
       	// Case #1.
       	无功灯
       	lamp.On()
       	lamp.Off()
       	/ /输出:
       	// on
       	// off
	
       	//情况#2.
       	Lamp{}.On()
       	Lamp{}.Off()
       	/ /输出: 
       	// on
       	// off
}
  • 当您需要一个通道来发送事件信号,但实际上不需要发送任何数据时. 此事件也不是序列中的最后一个事件,在这种情况下,您将使用 close(ch) 内置函数.
函数worker(ch chan struct{}) {
	//从主程序接收消息.
	<-ch
	println(“罗杰”)
	
	//发送消息给主程序.
	close(ch)
}

函数main() {
	= make(chan struct{})
	工人(ch)
	
	//向worker发送消息.
	ch <- struct{}{}
	
	//从worker接收消息.
	<-ch
	println(“罗杰”)
	/ /输出:
	// roger
	// roger
}

面试不仅仅是棘手的技术问题, 所以这些只是作为一个指南. 并不是每一个值得雇佣的“A”候选人都能回答所有的问题, 回答所有问题也不能保证成为A级考生. 一天结束的时候, 招聘仍然是一门艺术,一门科学,需要大量的工作.

为什么Toptal

厌倦了面试候选人? 不知道该问什么才能让你得到一份好工作?

让Toptal为你找到最合适的人.

现在就雇佣一名顶尖的工程师

我们的独家Go工程师网络

想找一份Go工程师的工作?

让Toptal为你找到合适的工作.

申请成为一名高级工程师

工作机会从我们的网络

提出面试问题

提交的问题和答案将被审查和编辑, 并可能会或可能不会选择张贴, 由Toptal全权决定, LLC.

*所有字段均为必填项

招聘Go工程师?

寻找 工程师去? 看看Toptal的Go工程师吧.

瑞恩·马修·史密斯

自由围棋工程师

美国Toptal成员 Since 2018年9月12日

在他的职业生涯中, 瑞安一直与初创公司和小型软件商店合作,在那里,每个里程碑都是成败的关键. 他喜欢在AWS中使用Terraform管理代码的基础设施. Ryan用Go和Bash编写DevOps工具,并定期为开源社区做出贡献. Ryan也是使用Docker部署和维护Kubernetes系统的专家. 根据项目的需要,Ryan擅长作为技术领导或与团队一起工作.

Show More

Asad Jibran Ahmed

自由围棋工程师

阿拉伯联合酋长国Toptal成员 Since 2018年2月14日

在过去的职位上,多名经理将Jibran描述为一名注重产品的工程师. 10年以上的工作经验, 他培养了使用软件开发识别和解决客户问题的技能. 在各种规模的公司工作过, 从4人创业到8人创业,000人强跨国(条纹), 他还有一种独特的视角,可以看到在不同的尺度上什么可行,什么不可行. Jibran利用这些经验来建立团队和系统,以适应业务的发展.

Show More

贾斯汀Michela

自由围棋工程师

美国Toptal成员 Since 2018年6月26日

Justin是一名技术专业人士,对学习充满热情,拥有18年以上领导团队构建企业级分布式应用程序解决现实问题的经验. 贾斯汀坚信,企业的各个方面都需要合作, 从开发到市场再到销售, 是成功的必要条件吗.

Show More

Toptal连接 Top 3% 世界各地的自由职业人才.

加入Toptal社区.

了解更多