圖片
今天刷脈脈的時候, 發現百度副總裁璩靜一個人竟然占了前三的兩個熱榜, 對于她的離職你怎么看?
言歸正傳, 本文的重點還是分享面經干貨
今天分享的是一位朋友在騰訊互娛的面經, 他本人目前已經是收到offer了, 讓我們來看看這個難度如何:
圖片
接口在Golang中扮演著連接不同類型之間的橋梁,它定義了一組方法的集合,而不關心具體的實現。接口的作用主要體現在以下幾個方面:
多態性:
接口允許不同的類型實現相同的方法,從而實現多態性。這意味著我們可以使用接口類型來處理不同的對象,而不需要關心具體的類型。
package mainimport "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 mainimport "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 mainimport "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()方法即可,而不需要修改已有的代碼。這就實現了代碼的可擴展性。
不包含任何字段的結構體,就叫做空結構體。
空結構體的特點:
在 Go 語言中,雖然沒有內置 Set 集合類型,但是我們可以利用 map 類型來實現一個 Set 集合。由于 map 的 key 具有唯一性,我們可以將元素存儲為 key,而 value 沒有實際作用,為了節省內存,我們可以使用空結構體作為 value 的值。
package mainimport "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) }() // 阻塞,等待退出信號被關閉 <-quit fmt.Println("已收到退出信號,退出中...") }
有時候我們需要創建一組方法集的實現(一般來說是實現一個接口),但并不需要在這個實現中存儲任何數據,這種情況下,我們可以使用空結構體來實現:
type Person interface { SayHello() Sleep()}type CMY struct{}func (c CMY) SayHello() { fmt.Println("你好,世界。")}func (c CMY) Sleep() { fmt.Println("晚安,世界...")}
默認參數是指在函數調用時,如果沒有提供某個參數的值,那么使用函數定義中指定的默認值。這種語言特性可以減少代碼量,簡化函數的使用。
在Go語言中,函數不支持默認參數。這意味著如果我們想要設置默認值,那么就需要手動在函數內部進行處理。
例如,下面是一個函數用于計算兩個整數的和:
func Add(a int, b int) int { return a + b}
如果我們希望b參數有一個默認值,例如為0,那么可以在函數內部進行處理:
func AddWithDefault(a int, b int) int { if b == 0 { b = 0 } return a + b}
上面的代碼中,如果b參數沒有提供值,那么默認為0。通過這種方式,我們就實現了函數的默認參數功能。
需要注意的是,這種處理方式雖然可以實現默認參數的效果,但會增加代碼復雜度和維護難度,因此在Go語言中不被推薦使用。
可選參數是指在函數調用時,可以省略一些參數的值,從而讓函數更加靈活。這種語言特性可以讓函數更加易用,提高代碼的可讀性。
在Go語言中,函數同樣不支持可選參數。但是,我們可以使用可變參數來模擬可選參數的效果。
下面是一個函數用于計算任意個整數的和:
func Add(nums ...int) int { sum := 0 for _, num := range nums { sum += num } return sum}
上面的代碼中,我們使用...int類型的可變參數來接收任意個整數,并在函數內部進行求和處理。
如果我們希望b和c參數為可選參數,那么可以將它們放到nums可變參數之后:
func AddWithOptional(a int, nums ...int) int { sum := a for _, num := range nums { sum += num } return sum}
上面的代碼中,我們首先將a參數賦值給sum變量,然后對可變參數進行求和處理。如果函數調用時省略了nums參數,則sum等于a的值。
需要注意的是,使用可變參數模擬可選參數的效果雖然能夠實現函數的靈活性,但也會降低代碼的可讀性和規范性。因此在Go語言中不被推薦使用。
在 Go 中,defer 語句用于延遲(defer)函數的執行,通常用于在函數執行結束前執行一些清理或收尾工作。當函數中存在多個 defer 語句時,它們的執行順序是“后進先出”(Last In First Out,LIFO)的,即最后一個被延遲的函數最先執行,倒數第二個被延遲的函數次之,以此類推。
在 Go 中,defer 語句中的函數在執行時會被壓入一個棧中,當函數執行結束時,這些被延遲的函數會按照后進先出的順序執行。這意味著在函數中的 defer 語句中的函數會在函數執行結束前執行,包括在 return 語句之前執行。
協程(Goroutine)之間的信息同步通常通過通道(Channel)來實現。通道是 Go 語言中用于協程之間通信的重要機制,可以安全地在不同協程之間傳遞數據,實現協程之間的信息同步。
一些常見的方法來實現協程之間的信息同步:
最開始的是GM模型沒有 P 的,是M:N的兩級線程模型,但是會出現一些性能問題:
轉載本文請聯系「王中陽Go」公眾號。
本文鏈接:http://www.tebozhan.com/showinfo-26-87961-0.html騰訊互娛面經詳解
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 這是一篇給Java初學者看的JVM文章
下一篇: 異步編程在C#中的應用:深入理解Task