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
}
~~~
運行結果:
~~~
0
40
50
~~~
該實例可以在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)
}
}
~~~
運行結果:
~~~
key=zhou,value=&{wang 22}
key=li,value=&{wang 22}
key=wang,value=&{wang 22}
~~~
答:
輸出的均是相同的值:&{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、下面的代碼會輸出什么,并說明原因
~~~
package main
import (
"fmt"
"runtime"
"sync"
)
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()
}
~~~
運行結果:
~~~
i: 9
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8
~~~
答:
將隨機輸出數字,但前面一個循環中并不會輸出所有值。
解析:
實際上第一行是否設置CPU為1都不會影響后續代碼。
2017年7月25日:將GOMAXPROCS設置為1,將影響goroutine的并發,后續代碼中的go func()相當于串行執行。
兩個for循環內部go func 調用參數i的方式是不同的,導致結果完全不同。這也是新手容易遇到的坑。
第一個go func中i是外部for的一個變量,地址不變化。遍歷完成后,最終i=10。故go func執行時,i的值始終是10(10次遍歷很快完成)。
第二個go func中i是函數參數,與外部for中的i完全是兩個變量。尾部(i)將發生值拷貝,go func內部指向值拷貝地址。
4、下面代碼會輸出什么?
~~~
package main
import (
"fmt"
)
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、下面代碼會觸發異常嗎?請詳細說明
~~~
package main
import (
"fmt"
"runtime"
)
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、下面代碼輸出什么?
~~~
package main
import (
"fmt"
)
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
a := 1 //line 1
b := 2 //2
defer calc("1", a, calc("10", a, b)) //3
a = 0 //4
defer calc("2", a, calc("20", a, b)) //5
b = 1 //6
}
~~~
答:
運行結果:
~~~
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 2、1 1 3 4 。
7、請寫出以下輸入內容
~~~
package main
import (
"fmt"
)
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、下面的代碼有什么問題?
~~~
package main
import (
"fmt"
"sync"
)
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
}
func main() {
userAges := &UserAges{
ages: make(map[string]int),
}
for i := 0; i < 10000; i++ {
go userAges.Add("oldboy", i)
go func() {
age := userAges.Get("oldboy")
fmt.Println(age)
}()
}
}
~~~
答:
在執行 Get方法時可能被panic
解析:
雖然有使用sync.Mutex做寫鎖,但是map是并發讀寫不安全的。map屬于引用類型,并發讀寫時多個協程見是通過指針訪問同一個地址,即訪問共享變量,此時同時讀寫資源存在競爭關系。會報錯誤信息:“fatal error: concurrent map read and map write”。
可以在在線運行中執行,復現該問題。那么如何改善呢? 當然Go1.9新版本中將提供并發安全的map。首先需要了解兩種鎖的不同:
sync.Mutex互斥鎖
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()
~~~
正確代碼:
~~~
package main
import (
"fmt"
"sync"
)
type UserAges struct {
ages map[string]int
sync.RWMutex
}
func (ua *UserAges) Add(name string, age int) {
ua.Lock()
defer ua.Unlock()
ua.ages[name] = age
}
func (ua *UserAges) Get(name string) int {
ua.Lock()
defer ua.Unlock()
if age, ok := ua.ages[name]; ok {
return age
}
return -1
}
func main() {
userAges := &UserAges{
ages: make(map[string]int),
}
for i := 0; i < 10000; i++ {
go userAges.Add("oldboy", i)
go func() {
age := userAges.Get("oldboy")
fmt.Println(age)
}()
}
}
~~~
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
}
~~~
答:
內部迭代出現阻塞。默認初始化時無緩沖區,需要等待接收者讀取后才能繼續寫入。
解析:
chan在使用make初始化時可附帶一個可選參數來設置緩沖區。默認無緩沖,題目中便初始化的是無緩沖區的chan,這樣只有寫入的元素直到被讀取后才能繼續寫入,不然就一直阻塞。
設置緩沖區大小后,寫入數據時可連續寫入到緩沖區中,直到緩沖區被占滿。從chan中接收一次便可從緩沖區中釋放一次。可以理解為chan是可以設置吞吐量的處理池。
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是值類型。
修改方法:
定義為指針:
~~~
var peo People = &Stduent{}
~~~
方法定義在值類型上,指針類型本身是包含值類型的方法。
~~~
func (stu Stduent) Speak(think string) (talk string) {
//...
}
~~~
- 序言
- 目錄
- 環境搭建
- Linux搭建golang環境
- Windows搭建golang環境
- Mac搭建golang環境
- 介紹
- 1.Go語言的主要特征
- 2.golang內置類型和函數
- 3.init函數和main函數
- 4.包
- 1.工作空間
- 2.源文件
- 3.包結構
- 4.文檔
- 5.編寫 Hello World
- 6.Go語言 “ _ ”(下劃線)
- 7.運算符
- 8.命令
- 類型
- 1.變量
- 2.常量
- 3.基本類型
- 1.基本類型介紹
- 2.字符串String
- 3.數組Array
- 4.類型轉換
- 4.引用類型
- 1.引用類型介紹
- 2.切片Slice
- 3.容器Map
- 4.管道Channel
- 5.指針
- 6.自定義類型Struct
- 編碼格式轉換
- 流程控制
- 1.條件語句(if)
- 2.條件語句 (switch)
- 3.條件語句 (select)
- 4.循環語句 (for)
- 5.循環語句 (range)
- 6.循環控制Goto、Break、Continue
- 函數
- 1.函數定義
- 2.參數
- 3.返回值
- 4.匿名函數
- 5.閉包、遞歸
- 6.延遲調用 (defer)
- 7.異常處理
- 8.單元測試
- 壓力測試
- 方法
- 1.方法定義
- 2.匿名字段
- 3.方法集
- 4.表達式
- 5.自定義error
- 接口
- 1.接口定義
- 2.執行機制
- 3.接口轉換
- 4.接口技巧
- 面向對象特性
- 并發
- 1.并發介紹
- 2.Goroutine
- 3.Chan
- 4.WaitGroup
- 5.Context
- 應用
- 反射reflection
- 1.獲取基本類型
- 2.獲取結構體
- 3.Elem反射操作基本類型
- 4.反射調用結構體方法
- 5.Elem反射操作結構體
- 6.Elem反射獲取tag
- 7.應用
- json協議
- 1.結構體轉json
- 2.map轉json
- 3.int轉json
- 4.slice轉json
- 5.json反序列化為結構體
- 6.json反序列化為map
- 終端讀取
- 1.鍵盤(控制臺)輸入fmt
- 2.命令行參數os.Args
- 3.命令行參數flag
- 文件操作
- 1.文件創建
- 2.文件寫入
- 3.文件讀取
- 4.文件刪除
- 5.壓縮文件讀寫
- 6.判斷文件或文件夾是否存在
- 7.從一個文件拷貝到另一個文件
- 8.寫入內容到Excel
- 9.日志(log)文件
- server服務
- 1.服務端
- 2.客戶端
- 3.tcp獲取網頁數據
- 4.http初識-瀏覽器訪問服務器
- 5.客戶端訪問服務器
- 6.訪問延遲處理
- 7.form表單提交
- web模板
- 1.渲染終端
- 2.渲染瀏覽器
- 3.渲染存儲文件
- 4.自定義io.Writer渲染
- 5.模板語法
- 時間處理
- 1.格式化
- 2.運行時間
- 3.定時器
- 鎖機制
- 互斥鎖
- 讀寫鎖
- 性能比較
- sync.Map
- 原子操作
- 1.原子增(減)值
- 2.比較并交換
- 3.導入、導出、交換
- 加密解密
- 1.md5
- 2.base64
- 3.sha
- 4.hmac
- 常用算法
- 1.冒泡排序
- 2.選擇排序
- 3.快速排序
- 4.插入排序
- 5.睡眠排序
- 設計模式
- 創建型模式
- 單例模式
- 抽象工廠模式
- 工廠方法模式
- 原型模式
- 結構型模式
- 適配器模式
- 橋接模式
- 合成/組合模式
- 裝飾模式
- 外觀模式
- 享元模式
- 代理模式
- 行為性模式
- 職責鏈模式
- 命令模式
- 解釋器模式
- 迭代器模式
- 中介者模式
- 備忘錄模式
- 觀察者模式
- 狀態模式
- 策略模式
- 模板模式
- 訪問者模式
- 數據庫操作
- golang操作MySQL
- 1.mysql使用
- 2.insert操作
- 3.select 操作
- 4.update 操作
- 5.delete 操作
- 6.MySQL事務
- golang操作Redis
- 1.redis介紹
- 2.golang鏈接redis
- 3.String類型 Set、Get操作
- 4.String 批量操作
- 5.設置過期時間
- 6.list隊列操作
- 7.Hash表
- 8.Redis連接池
- golang操作ETCD
- 1.etcd介紹
- 2.鏈接etcd
- 3.etcd存取
- 4.etcd監聽Watch
- golang操作kafka
- 1.kafka介紹
- 2.寫入kafka
- 3.kafka消費
- golang操作ElasticSearch
- 1.ElasticSearch介紹
- 2.kibana介紹
- 3.寫入ElasticSearch
- NSQ
- 安裝
- 生產者
- 消費者
- beego框架
- 1.beego框架環境搭建
- 2.參數配置
- 1.默認參數
- 2.自定義配置
- 3.config包使用
- 3.路由設置
- 1.自動匹配
- 2.固定路由
- 3.正則路由
- 4.注解路由
- 5.namespace
- 4.多種數據格式輸出
- 1.直接輸出字符串
- 2.模板數據輸出
- 3.json格式數據輸出
- 4.xml格式數據輸出
- 5.jsonp調用
- 5.模板處理
- 1.模板語法
- 2.基本函數
- 3.模板函數
- 6.請求處理
- 1.GET請求
- 2.POST請求
- 3.文件上傳
- 7.表單驗證
- 1.表單驗證
- 2.定制錯誤信息
- 3.struct tag 驗證
- 4.XSRF過濾
- 8.靜態文件處理
- 1.layout設計
- 9.日志處理
- 1.日志處理
- 2.logs 模塊
- 10.會話控制
- 1.會話控制
- 2.session 包使用
- 11.ORM 使用
- 1.鏈接數據庫
- 2. CRUD 操作
- 3.原生 SQL 操作
- 4.構造查詢
- 5.事務處理
- 6.自動建表
- 12.beego 驗證碼
- 1.驗證碼插件
- 2.驗證碼使用
- beego admin
- 1.admin安裝
- 2.admin開發
- beego 熱升級
- gin框架
- 安裝使用
- 項目
- 秒殺項目
- 日志收集
- 面試題
- 面試題一
- 面試題二
- 錯題集
- Go語言陷阱和常見錯誤
- 常見語法錯誤
- 初級
- 中級
- 高級
- Go高級應用
- goim
- goim 啟動流程
- goim 工作流程
- goim 結構體
- gopush
- gopush工作流程
- gopush啟動流程
- gopush業務流程
- gopush應用
- gopush新添功能
- rpc
- HTTP RPC
- TCP RPC
- JSON RPC
- 常見RPC開源框架
- pprof
- pprof介紹
- pprof應用
- 封裝 websocket
- zookeeper
- 基本操作測試
- 簡單的分布式server
- Zookeeper命令行使用
- cgo
- Go語言 demo
- 用Go語言計算一個人的年齡,生肖,星座
- 超簡易Go語言實現的留言板代碼
- 信號處理模塊,可用于在線加載配置,配置動態加載的信號為SIGHUP
- 陽歷和陰歷相互轉化的工具類 golang版本
- 錯誤總結