10个面试必备问题 *
Toptal提供了最好的围棋工程师可以回答的基本问题. 在我们社区的推动下,我们鼓励专家提交问题并提供反馈.
现在就雇佣一名顶尖的工程师面试问题
交换两个值就像这样简单:
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]
}
您可以使用内置的 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()
功能不存在.
可以比较两个结构体 ==
操作符,就像处理其他简单类型一样. 只要确保它们不包含任何切片, 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 ()
.
下面的代码片段有什么问题?
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)
}
你可以自己使用切片来实现堆栈或队列:
类型栈[]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:
- 缓冲区大小在通道创建时确定,不能增加.
- 如果不从队列中检索下一个队列元素,就不可能看到它.
- 存在死锁的风险:“新手有时会尝试在单个程序中使用缓冲通道作为队列, 被它们令人愉悦的简单语法所吸引, 但这是一个错误. 通道与日常调度密切相关, 并且不需要另一个从通道接收的程序, 一个发送者——也许是整个程序——有可能永远被封锁. 如果您只需要一个简单的队列,那就使用切片创建一个.”, Brian Kernighan.
下面的小程序可能有什么问题?
函数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.
你需要对哈希的键进行排序.
水果:= map[string]int{
“橙子”:100年,
“苹果”:200年,
“香蕉”:300年,
}
//将键放入切片并对其进行排序.
Var keys []string
对于键:=范围水果{
键= append(键,键)
}
sort.字符串(键)
//根据排序的切片显示键.
对于_,键:=范围键{
fmt.Printf("%s:%v\n",键,水果[键])
}
/ /输出:
/ /苹果:200
/ /香蕉:300
/ /橙子:100
如果片未被使用,第一个声明不会分配内存, 因此,这种声明方法是首选的.
大多数时候,你并不需要两者都需要. 你只需要 GOPATH
变量集指向Go包树.
GOROOT
指向Go语言主目录的根目录, 但它很可能已经设置为当前Go语言安装的目录. 很容易检查是否如此 go env
command:
$ go env
…
GOROOT = " / home / zabb /去”
…
有必要设置 GOROOT
如果在同一系统上有多个Go语言版本,或者Go语言是作为二进制包从互联网下载的,或者是从另一个系统传输的,则变量.
当您想要节省一些内存时,可以使用空结构体. 空结构体的值不占用任何内存.
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全权决定, LLC.
Toptal连接 Top 3% 世界各地的自由职业人才.
加入Toptal社区.