先来唠唠
图片
今天刷脉脉的时候, 发现百度副总裁璩静一个人竟然占了前三的两个热榜, 对于她的离职你怎么看?
言归正传, 本文的重点还是分享面经干货
今天分享的是一位朋友在腾讯互娱的面经, 他本人目前已经是收到offer了, 让我们来看看这个难度如何:
图片
面试题详解
Go接口
接口在Golang中扮演着连接不同类型之间的桥梁,它定义了一组方法的集合,而不关心具体的实现。接口的作用主要体现在以下几个方面:
多态性:
接口允许不同的类型实现相同的方法,从而实现多态性。这意味着我们可以使用接口类型来处理不同的对象,而不需要关心具体的类型。
package main
import "fmt"
type Animal interface {
Sound() string
}
type Dog struct{}
func (d Dog) Sound() string {
return "Woof!"
}
type Cat struct{}
func (c Cat) Sound() string {
return "Meow!"
}
func main() {
animals := []Animal{Dog{}, Cat{}}
for _, animal := range animals {
fmt.Println(animal.Sound())
}
}
在上面的示例中,我们定义了一个Animal接口,它包含了一个Sound()方法。然后,我们实现了Dog和Cat两个结构体,分别实现了Sound()方法。通过将Dog和Cat类型赋值给Animal接口类型,我们可以在循环中调用Sound()方法,而不需要关心具体的类型。这就体现了接口的多态性,不同的类型可以实现相同的接口方法。
解耦合:
接口可以将抽象与实现分离,降低代码之间的耦合度。通过定义接口,我们可以将实现细节隐藏起来,只暴露必要的方法,从而提高代码的可维护性和可读性。
package main
import "fmt"
type Printer interface {
Print(string)
}
type ConsolePrinter struct{}
func (cp ConsolePrinter) Print(message string) {
fmt.Println(message)
}
type FilePrinter struct{}
func (fp FilePrinter) Print(message string) {
// 将消息写入文件
fmt.Println("Writing message to file:", message)
}
func main() {
printer := ConsolePrinter{}
printer.Print("Hello, World!")
printer = FilePrinter{}
printer.Print("Hello, World!")
}
在上面的示例中,我们定义了一个Printer接口,它包含了一个Print()方法。然后,我们实现了ConsolePrinter和FilePrinter两个结构体,分别实现了Print()方法。通过将不同的结构体赋值给Printer接口类型的变量,我们可以在主函数中调用Print()方法,而不需要关心具体的实现。这样,我们可以根据需要轻松地切换不同的打印方式,实现了解耦合。
可扩展性:
package main
import "fmt"
type Shape interface {
Area() float64
}
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func main() {
shapes := []Shape{Rectangle{Width: 5, Height: 10}, Circle{Radius: 3}}
for _, shape := range shapes {
fmt.Println("Area:", shape.Area())
}
}
在上面的示例中,我们定义了一个Shape接口,它包含了一个Area()方法。然后,我们实现了Rectangle和Circle两个结构体,分别实现了Area()方法。通过将不同的结构体赋值给Shape接口类型的切片,我们可以在循环中调用Area()方法,而不需要关心具体的类型。这样,当我们需要添加新的形状时,只需要实现Shape接口的Area()方法即可,而不需要修改已有的代码。这就实现了代码的可扩展性。
接口的应用场景
空结构体的用途
不包含任何字段的结构体,就叫做空结构体。
空结构体的特点:
空结构体的使用场景
- 实现set集合
在 Go 语言中,虽然没有内置 Set 集合类型,但是我们可以利用 map 类型来实现一个 Set 集合。由于 map 的 key 具有唯一性,我们可以将元素存储为 key,而 value 没有实际作用,为了节省内存,我们可以使用空结构体作为 value 的值。
package main
import "fmt"
type Set[K comparable] map[K]struct{}
func (s Set[K]) Add(val K) {
s[val] = struct{}{}
}
func (s Set[K]) Remove(val K) {
delete(s, val)
}
func (s Set[K]) Contains(val K) bool {
_, ok := s[val]
return ok
}
func main() {
set := Set[string]{}
set.Add("程序员")
fmt.Println(set.Contains("程序员")) // true
set.Remove("程序员")
fmt.Println(set.Contains("程序员")) // false
}
- 用于通道信号
空结构体常用于 Goroutine 之间的信号传递,尤其是不关心通道中传递的具体数据,只需要一个触发信号时。例如,我们可以使用空结构体通道来通知一个 Goroutine 停止工作:
package main
import (
"fmt"
"time"
)
func main() {
quit := make(chan struct{})
go func() {
// 模拟工作
fmt.Println("工作中...")
time.Sleep(3 * time.Second)
// 关闭退出信号
close(quit)
}()
// 阻塞,等待退出信号被关闭
操作符带索引的列
range:只检索给定范围的行,使用一个索引来选择行。一般使用between、>、7->19->26,发现26比23大,就回到19
再用第1层连接19->22->26,发现比23大,那么就插入到26之前,22之后
上面这张图就是跳表的初步原理,但一个元素插入链表后,应该拥有几层连接呢?跳表在这块的实现方式是随机的,也就是23这个元素插入后,随机出一个数,比如这个数是3,那么23就会有如下连接:
- 第3层head->23->end
- 第2层19->23->26
- 第1层22->23->26
下面这张图展示了如何形成一个跳表
图片
在上述跳表中查找/插入23的过程为:
图片
总结一下跳表原理:
- 每个跳表都必须设定一个最大的连接层数MaxLevel
- 第一层连接会连接到表中的每个元素
- 插入一个元素会随机生成一个连接层数值[1, MaxLevel]之间,根据这个值跳表会给这元素建立N个连接
- 插入某个元素的时候先从最高层开始,当跳到比目标值大的元素后,回退到上一个元素,用该元素的下一层连接进行遍历,周而复始直到第一层连接,最终在第一层连接中找到合适的位置
使用场景:
- 对有序数据进行排序,例如新闻排行榜或游戏排行榜。
- 对数据进行分组,例如将所有评分在3.0 到4.0 之间的电影分为一组。
- 对数据进行去重,例如将所有重复的单词从文本中删除。
本文转载自微信公众号「王中阳Go」,作者「王中阳Go」,可以通过以下二维码关注。
转载本文请联系「王中阳Go」公众号。