欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

探索Go語言中的switch高級用法

 更新時間:2024年10月01日 16:37:26   作者:apocelipes  
在Go語言中,switch語句除了常見的用法外,還有一種不常用但有趣的寫法,這種寫法中,switch后面不跟任何表達式,而每個case后面跟的是返回bool類型的函數(shù)調用表達式,這實際上是一個等價于switch true的用法,通過從上到下逐一比較case后的表達式是否為true來決定執(zhí)行哪個分支

最近翻開源代碼的時候看到了一種很有意思的switch用法,分享一下。

注意這里討論的不是typed switch,也就是case語句后面是類型的那種。

直接看代碼:

	func (s *systemd) Status() (Status, error) {

	exitCode, out, err := s.runWithOutput("systemctl", "is-active", s.unitName())

	if exitCode == 0 && err != nil {

	return StatusUnknown, err

	}

	


	switch {

	case strings.HasPrefix(out, "active"):

	return StatusRunning, nil

	case strings.HasPrefix(out, "inactive"):

	// inactive can also mean its not installed, check unit files

	exitCode, out, err := s.runWithOutput("systemctl", "list-unit-files", "-t", "service", s.unitName())

	if exitCode == 0 && err != nil {

	return StatusUnknown, err

	}

	if strings.Contains(out, s.Name) {

	// unit file exists, installed but not running

	return StatusStopped, nil

	}

	// no unit file

	return StatusUnknown, ErrNotInstalled

	case strings.HasPrefix(out, "activating"):

	return StatusRunning, nil

	case strings.HasPrefix(out, "failed"):

	return StatusUnknown, errors.New("service in failed state")

	default:

	return StatusUnknown, ErrNotInstalled

	}

	}

你也可以在這找到它:代碼鏈接

簡單解釋下這段代碼在做什么:調用systemctl命令檢查指定的服務的運行狀態(tài),具體做法是過濾systemctl的輸出然后根據(jù)得到的字符串的前綴判斷當前的運行狀態(tài)。

有意思的在于這個switch,首先它后面沒有任何表達式;其次在每個case后面都是個函數(shù)調用表達式,返回值都是bool類型的。

雖然看起來很怪異,但這段代碼肯定沒有語法問題,可以編譯通過;也沒有語義或者邏輯問題,因為人家用的好好的,這個項目接近4000個星星不是大家亂點的。

這里就不賣關子了,直接公布答案:

  • 如果switch后面沒有任何表達式,那么它等價于這個:switch true
  • case表達式按從上到下從左到右的順序求值;
  • 如果case后面的表達式求出來的值和switch后面的表達式的值一樣,那么就進入這個分支,其他case被忽略(除非用了fallthrough,但這會直接跳進下一個case的分支,不會執(zhí)行下一個case上的表達式)。

那么上面那一串代碼就好理解了:

  • 首先是switch true,期待有個case能求出true這個值;
  • 從上到下執(zhí)行strings.HasPrefix,如果是false就往下到下一個case,如果是true就進入這個case的分支。

它等價于下面這段:

	func (s *systemd) Status() (Status, error) {

	exitCode, out, err := s.runWithOutput("systemctl", "is-active", s.unitName())

	if exitCode == 0 && err != nil {

	return StatusUnknown, err

	}

	


	if strings.HasPrefix(out, "active") {

	return StatusRunning, nil

	}

	if strings.HasPrefix(out, "inactive") {

	// inactive can also mean its not installed, check unit files

	exitCode, out, err := s.runWithOutput("systemctl", "list-unit-files", "-t", "service", s.unitName())

	if exitCode == 0 && err != nil {

	return StatusUnknown, err

	}

	if strings.Contains(out, s.Name) {

	// unit file exists, installed but not running

	return StatusStopped, nil

	}

	// no unit file

	return StatusUnknown, ErrNotInstalled

	}

	if strings.HasPrefix(out, "activating") {

	return StatusRunning, nil

	}

	if strings.HasPrefix(out, "failed") {

	return StatusUnknown, errors.New("service in failed state")

	}

	


	return StatusUnknown, ErrNotInstalled

	}

可以看到,光從可讀性上來說的話兩者很難說誰更優(yōu)秀;兩者同樣需要注意把常見的情況放在最前面來減少不必要的匹配(這里的switch-case不能像給整數(shù)常量時那樣直接進行跳轉,實際執(zhí)行和上面給出的if語句是差不多的)。

那么我們再來看看兩者的生成代碼,通常我不喜歡去研究編譯器生成的代碼,但這次是個小例外,對于執(zhí)行流程上很接近的兩段代碼,編譯器會怎么處理呢?

我們做個簡化版的例子:

func status1(cmdOutput string, flag int) int {
	switch {
	case strings.HasPrefix(cmdOutput, "active"):
	return 1
	case strings.HasPrefix(cmdOutput, "inactive"):
	if flag > 0 {
	return 2
	}
	return -1
	case strings.HasPrefix(cmdOutput, "activating"):
	return 1
	case strings.HasPrefix(cmdOutput, "failed"):
	return -1
	default:
	return -2
	}
	}
	func status2(cmdOutput string, flag int) int {
	if strings.HasPrefix(cmdOutput, "active") {
	return 1
	}
	if strings.HasPrefix(cmdOutput, "inactive") {
	if flag > 0 {
	return 2
	}
	return -1
	}
	if strings.HasPrefix(cmdOutput, "activating") {
	return 1
	}
	if strings.HasPrefix(cmdOutput, "failed") {
	return -1
	}
	return -2
	}

這是switch版本的匯編:

	main_status1_pc0:

	TEXT main.status1(SB), ABIInternal, $40-24

	CMPQ SP, 16(R14)

	PCDATA $0, $-2

	JLS main_status1_pc273

	PCDATA $0, $-1

	SUBQ $40, SP

	MOVQ BP, 32(SP)

	LEAQ 32(SP), BP

	FUNCDATA $0, gclocals·wgcWObbY2HYnK2SU/U22lA==(SB)

	FUNCDATA $1, gclocals·J5F+7Qw7O7ve2QcWC7DpeQ==(SB)

	FUNCDATA $5, main.status1.arginfo1(SB)

	FUNCDATA $6, main.status1.argliveinfo(SB)

	PCDATA $3, $1

	MOVQ CX, main.flag+64(SP)

	MOVQ AX, main.cmdOutput+48(SP)

	MOVQ BX, main.cmdOutput+56(SP)

	PCDATA $3, $-1

	MOVL $6, DI

	LEAQ go:string."active"(SB), CX

	PCDATA $1, $0

	CALL strings.HasPrefix(SB)

	NOP

	TESTB AL, AL

	JNE main_status1_pc258

	MOVQ main.cmdOutput+48(SP), AX

	MOVQ main.cmdOutput+56(SP), BX

	LEAQ go:string."inactive"(SB), CX

	MOVL $8, DI

	NOP

	CALL strings.HasPrefix(SB)

	TESTB AL, AL

	JEQ main_status1_pc147

	MOVQ main.flag+64(SP), CX

	TESTQ CX, CX

	JLE main_status1_pc130

	MOVL $2, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status1_pc130:

	MOVQ $-1, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status1_pc147:

	MOVQ main.cmdOutput+48(SP), AX

	MOVQ main.cmdOutput+56(SP), BX

	LEAQ go:string."activating"(SB), CX

	MOVL $10, DI

	CALL strings.HasPrefix(SB)

	TESTB AL, AL

	JNE main_status1_pc243

	MOVQ main.cmdOutput+48(SP), AX

	MOVQ main.cmdOutput+56(SP), BX

	LEAQ go:string."failed"(SB), CX

	MOVL $6, DI

	PCDATA $1, $1

	CALL strings.HasPrefix(SB)

	TESTB AL, AL

	JEQ main_status1_pc226

	MOVQ $-1, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status1_pc226:

	MOVQ $-2, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status1_pc243:

	MOVL $1, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status1_pc258:

	MOVL $1, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status1_pc273:

	NOP

	PCDATA $1, $-1

	PCDATA $0, $-2

	MOVQ AX, 8(SP)

	MOVQ BX, 16(SP)

	MOVQ CX, 24(SP)

	CALL runtime.morestack_noctxt(SB)

	MOVQ 8(SP), AX

	MOVQ 16(SP), BX

	MOVQ 24(SP), CX

	PCDATA $0, $-1

	JMP main_status1_pc0

我把inline給關了,不然hasprefix內聯(lián)出來的東西會導致整個匯編代碼難以閱讀。

上面的代碼還是很好理解的,“active”和“inactive”的case被放在一起,如果匹配到了就跳轉進入對應的分支;“activing”和“failed”的case也放在了一起,匹配到之后的操作與前面兩個case一樣(實際上上面兩個case的匹配執(zhí)行完就會跳轉到這兩個,至于為啥要多一次跳轉我沒深究,可能是為了提高L1d的命中率,一大塊指令可能會導致緩存里放不下從而付出更新緩存的代價,而有流水線優(yōu)化的情況下一個jmp帶來的開銷可能低于緩存未命中的懲罰,不過這在實踐里很難測量,權當我在自言自語也行)。最后那一串帶ret的語句塊就是對應的case的分支。

再來看看if的代碼:

	main_status2_pc0:

	TEXT main.status2(SB), ABIInternal, $40-24

	CMPQ SP, 16(R14)

	PCDATA $0, $-2

	JLS main_status2_pc273

	PCDATA $0, $-1

	SUBQ $40, SP

	MOVQ BP, 32(SP)

	LEAQ 32(SP), BP

	FUNCDATA $0, gclocals·wgcWObbY2HYnK2SU/U22lA==(SB)

	FUNCDATA $1, gclocals·J5F+7Qw7O7ve2QcWC7DpeQ==(SB)

	FUNCDATA $5, main.status2.arginfo1(SB)

	FUNCDATA $6, main.status2.argliveinfo(SB)

	PCDATA $3, $1

	MOVQ CX, main.flag+64(SP)

	MOVQ AX, main.cmdOutput+48(SP)

	MOVQ BX, main.cmdOutput+56(SP)

	PCDATA $3, $-1

	MOVL $6, DI

	LEAQ go:string."active"(SB), CX

	PCDATA $1, $0

	CALL strings.HasPrefix(SB)

	NOP

	TESTB AL, AL

	JNE main_status2_pc258

	MOVQ main.cmdOutput+48(SP), AX

	MOVQ main.cmdOutput+56(SP), BX

	LEAQ go:string."inactive"(SB), CX

	MOVL $8, DI

	NOP

	CALL strings.HasPrefix(SB)

	TESTB AL, AL

	JEQ main_status2_pc147

	MOVQ main.flag+64(SP), CX

	TESTQ CX, CX

	JLE main_status2_pc130

	MOVL $2, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status2_pc130:

	MOVQ $-1, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status2_pc147:

	MOVQ main.cmdOutput+48(SP), AX

	MOVQ main.cmdOutput+56(SP), BX

	LEAQ go:string."activating"(SB), CX

	MOVL $10, DI

	CALL strings.HasPrefix(SB)

	TESTB AL, AL

	JNE main_status2_pc243

	MOVQ main.cmdOutput+48(SP), AX

	MOVQ main.cmdOutput+56(SP), BX

	LEAQ go:string."failed"(SB), CX

	MOVL $6, DI

	PCDATA $1, $1

	CALL strings.HasPrefix(SB)

	TESTB AL, AL

	JEQ main_status2_pc226

	MOVQ $-1, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status2_pc226:

	MOVQ $-2, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status2_pc243:

	MOVL $1, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status2_pc258:

	MOVL $1, AX

	MOVQ 32(SP), BP

	ADDQ $40, SP

	RET

	main_status2_pc273:

	NOP

	PCDATA $1, $-1

	PCDATA $0, $-2

	MOVQ AX, 8(SP)

	MOVQ BX, 16(SP)

	MOVQ CX, 24(SP)

	CALL runtime.morestack_noctxt(SB)

	MOVQ 8(SP), AX

	MOVQ 16(SP), BX

	MOVQ 24(SP), CX

	PCDATA $0, $-1

	JMP main_status2_pc0

除了函數(shù)名子不一樣之外,其他是一模一樣的,可以說兩者在生成代碼上也沒有區(qū)別。

你可以在這里看到代碼和他們的編譯產物:Compiler Explorer

既然生成代碼是一樣的,那性能就沒必要測量了,因為肯定是一樣的。

最后總結一下這種不常用的switch寫法,形式如下:

	switch {

	case 表達式1: // 如果是true

	do works1

	case 表達式2: // 如果是true

	do works2

	default:

	都不是true就會到這里

	}

考慮到在性能上這并沒有什么優(yōu)勢,而且對于初次見到這個寫法的人可能不能很快理解它的含義,所以這個寫法的使用場景我目前能想到的只有一處:

如果你的數(shù)據(jù)有固定的2種以上的前綴/后綴/某種模式,因為沒法用固定的常量去表示這種情況,那么用case加上一個簡單的表達式(函數(shù)調用之類的)會比用if更緊湊,也能更好地表達語義,case越多效果越明顯。比如我在開頭舉的那個例子。

如果你的代碼不符合上述情況,那還是老老實實用if會更好。

話說回來,雖然你機會沒啥機會寫出這種switch語句,但最好還是得看懂,不然下回看見它就只能干瞪眼了。

參考

https://go.dev/ref/spec#Switch_statements

到此這篇關于探索Go語言中的switch高級用法的文章就介紹到這了,更多相關go中不常見的switch用法內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 剖析Go編寫的Socket服務器模塊解耦及基礎模塊的設計

    剖析Go編寫的Socket服務器模塊解耦及基礎模塊的設計

    這篇文章主要介紹了Go的Socket服務器模塊解耦及日志和定時任務的模塊設計,舉了一些Go語言編寫的服務器模塊的例子,需要的朋友可以參考下
    2016-03-03
  • Golang自定義結構體轉map的操作

    Golang自定義結構體轉map的操作

    這篇文章主要介紹了Golang自定義結構體轉map的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 基于Go語言搭建靜態(tài)文件服務器的詳細教程

    基于Go語言搭建靜態(tài)文件服務器的詳細教程

    Go 是一個開源的編程語言,它能讓構造簡單、可靠且高效的軟件變得容易,本文給大家介紹了基于Go語言搭建靜態(tài)文件服務器的詳細教程,文中通過圖文和代碼講解的非常詳細,需要的朋友可以參考下
    2024-10-10
  • Golang中的crypto/ecdh包使用詳解

    Golang中的crypto/ecdh包使用詳解

    這篇文章主要給大家詳細介紹了Golang 中的 crypto/ecdh 包,主要包括什么是ECDH 算法和crypto/ecdh 包的使用方法,文中通過代碼示例介紹的非常詳細,需要的朋友可以參考下
    2023-09-09
  • GPT回答 go語言和C語言數(shù)組操作對比

    GPT回答 go語言和C語言數(shù)組操作對比

    這篇文章主要為大家介紹了GPT回答的go語言和C語言數(shù)組操作方法對比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • Go語言實現(xiàn)的簡單網絡端口掃描方法

    Go語言實現(xiàn)的簡單網絡端口掃描方法

    這篇文章主要介紹了Go語言實現(xiàn)的簡單網絡端口掃描方法,實例分析了Go語言網絡程序的實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • golang sudog指的是什么

    golang sudog指的是什么

    sudog代表在等待隊列中的goroutine,比如channel發(fā)送接受,由于goroutine和同步對象的關系是多對多,因此需要sudog映射,本文重點介紹golang sudog指的是什么,感興趣的朋友一起看看吧
    2024-02-02
  • Go語言導出內容到Excel的方法

    Go語言導出內容到Excel的方法

    這篇文章主要介紹了Go語言導出內容到Excel的方法,涉及Go語言操作excel的技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • golang內存對齊的項目實踐

    golang內存對齊的項目實踐

    本文主要介紹了golang內存對齊的項目實踐,內存對齊不僅有助于提高內存訪問效率,還確保了與硬件接口的兼容性,是Go語言編程中不可忽視的重要優(yōu)化手段,下面就來介紹一下
    2025-02-02
  • Go中的格式化字符串fmt.Sprintf()和fmt.Printf()使用示例

    Go中的格式化字符串fmt.Sprintf()和fmt.Printf()使用示例

    這篇文章主要為大家介紹了Go中的格式化字符串fmt.Sprintf()和fmt.Printf()使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06

最新評論