golang中一种不常见的switch语句写法

这篇具有很好参考价值的文章主要介绍了golang中一种不常见的switch语句写法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近翻开源代码的时候看到了一种很有意思的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命令检查指定的服务的运行状态,具体做法是过滤systemctl的输出然后根据得到的字符串的前缀判断当前的运行状态。

有意思的在于这个switch,首先它后面没有任何表达式;其次在每个case后面都是个函数调用表达式,返回值都是bool类型的。

虽然看起来很怪异,但这段代码肯定没有语法问题,可以编译通过;也没有语义或者逻辑问题,因为人家用的好好的,这个项目接近4000个星星不是大家乱点的。

这里就不卖关子了,直接公布答案:

  1. 如果switch后面没有任何表达式,那么它等价于这个:switch true
  2. case表达式按从上到下从左到右的顺序求值;
  3. 如果case后面的表达式求出来的值和switch后面的表达式的值一样,那么就进入这个分支,其他case被忽略(除非用了fallthrough,但这会直接跳进下一个case的分支,不会执行下一个case上的表达式)。

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

  1. 首先是switch true,期待有个case能求出true这个值;
  2. 从上到下执行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
}

可以看到,光从可读性上来说的话两者很难说谁更优秀;两者同样需要注意把常见的情况放在最前面来减少不必要的匹配(这里的switch-case不能像给整数常量时那样直接进行跳转,实际执行和上面给出的if语句是差不多的)。

那么我们再来看看两者的生成代码,通常我不喜欢去研究编译器生成的代码,但这次是个小例外,对于执行流程上很接近的两段代码,编译器会怎么处理呢?

我们做个简化版的例子:

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内联出来的东西会导致整个汇编代码难以阅读。

上面的代码还是很好理解的,“active”和“inactive”的case被放在一起,如果匹配到了就跳转进入对应的分支;“activing”和“failed”的case也放在了一起,匹配到之后的操作与前面两个case一样(实际上上面两个case的匹配执行完就会跳转到这两个,至于为啥要多一次跳转我没深究,可能是为了提高L1d的命中率,一大块指令可能会导致缓存里放不下从而付出更新缓存的代价,而有流水线优化的情况下一个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

除了函数名子不一样之外,其他是一模一样的,可以说两者在生成代码上也没有区别。

你可以在这里看到代码和他们的编译产物:Compiler Explorer

既然生成代码是一样的,那性能就没必要测量了,因为肯定是一样的。

最后总结一下这种不常用的switch写法,形式如下:

switch {
case 表达式1: // 如果是true
    do works1
case 表达式2: // 如果是true
    do works2
default:
    都不是true就会到这里
}

考虑到在性能上这并没有什么优势,而且对于初次见到这个写法的人可能不能很快理解它的含义,所以这个写法的使用场景我目前能想到的只有一处:

如果你的数据有固定的2种以上的前缀/后缀/某种模式,因为没法用固定的常量去表示这种情况,那么用case加上一个简单的表达式(函数调用之类的)会比用if更紧凑,也能更好地表达语义,case越多效果越明显。比如我在开头举的那个例子。

如果你的代码不符合上述情况,那还是老老实实用if会更好。

话说回来,虽然你机会没啥机会写出这种switch语句,但最好还是得看懂,不然下回看见它就只能干瞪眼了。

参考

https://go.dev/ref/spec#Switch_statements文章来源地址https://www.toymoban.com/news/detail-432205.html

到了这里,关于golang中一种不常见的switch语句写法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Golang中for和for range语句的使用技巧、对比及常见的避坑

    2024.1.0更新: Go 团队将修改 for 循环变量的语义,Go1.21 新版本即可体验! 今天看见了这篇文章,Go的1.22版本将更新,大致理解未会默认进行 v:=v 这个操作,因此此文所概述的许多坑,在1.22之后都可能会更新。 2024.2就会发布新版本,到时候再测试一下看看情况。 基础语法不

    2024年02月01日
    浏览(62)
  • Python Switch 语句——Switch Case 示例

    在 3.10 版本之前,Python 从来没有实现 switch 语句在其他编程语言中所做的功能。 所以,如果你想执行多个条件语句,你将不得不使用elif这样的: 从 3.10 版本开始,Python 实现了一个称为“结构模式匹配”的 switch case 特性。您可以使用match和case来实现此功能。 有

    2024年02月12日
    浏览(53)
  • golang - switch

    switch 语句用于基于不同条件执行不同操作,,直 每一个 case 分支都是唯一的,从上到下逐一测试 到匹配为止 匹配项后面 也不需要再加 break   case 后是一个表达式(即:常量值、变量、一个有返回值的函数等都可以) case 后的各个表达式的值的数据类型,必须和 switch 的表达

    2024年02月02日
    浏览(55)
  • 「PHP系列」If...Else语句/switch语句

    PHP 中的 if...else 语句是用于根据条件执行不同代码块的强大工具。这种结构允许你基于某个条件(通常是布尔表达式)的结果来决定执行哪一部分代码。下面是对 if...else 语句的详细解释以及一些示例。 示例 1:基本 if…else 结构 在这个例子中,如果 $number 大于 5,将输出 “

    2024年04月27日
    浏览(39)
  • 【C++】switch 语句

    目录 1、缘起  2、笔记整理 3、if 和 switch 区别  4、总结         最近(2023-04-29)在 BiliBili 黑马程序员学习 C++  编程语言,今天学习到了 switch 语句 。以前在学习 C 语言  的时候,对这块知识点掌握的不是很好,总是遗忘。所以这次在学习 C++ 的时候,为了加强这块知

    2024年02月01日
    浏览(41)
  • Java 流程控制 Switch 语句

    一、什么是Switch语句? Switch语句是Java中的一种流程控制语句,用于根据表达式的值选择不同的执行路径。Switch语句通常用于多个条件的判断,比如根据用户输入的不同选项执行不同的操作。 二、语法说明 Switch语句的基本语法如下: Switch语句包含一个表达式和多个case语句块

    2024年02月09日
    浏览(49)
  • C#中的switch语句详解

    在C#编程语言中,switch语句是一种用于根据不同的条件执行不同代码块的控制流结构。它允许程序根据一个表达式的值,选择执行与该值相关联的特定代码块。本文将详细介绍switch语句的语法、用法和示例代码。 switch语句的基本语法如下: 首先,我们需要一个表达式,它的值

    2024年01月17日
    浏览(40)
  • C#系列之switch语句

    今天,我们将对于switch语句做一个较为简单的了解。在日后的Unity内部C#脚本的使用做好充分的准备。我们将通过一些实例展开今天的内容。 1.基本语法 switch(变量) { //变量 == 常量,执行case和break之间的代码 case 常量:          满足条件之下的代码逻辑;         break; c

    2024年02月11日
    浏览(44)
  • Java switch case 语句

    Java 的 switch case 语句是一种常用的控制流语句,用于基于不同的输入值执行不同的操作。本文将详细介绍 Java switch case 语句的作用、用法以及在实际工作中的应用。 一、switch case 语句的作用 switch case 语句是一种多分支条件语句,它基于不同的输入值,执行不同的操作。swit

    2024年02月11日
    浏览(38)
  • Kotlin单例模式的一种懒汉模式写法

    Kotlin单例模式的一种懒汉模式写法 kotlin用object实现单例模式,companion object与java静态_zhangphil的博客-CSDN博客 kotlin中很容易使用object实现java中的单例模式。由于kotlin中没有static修饰词,可以用companion object实现Java中的static效果。kotlin用object实现单例模式,companion object与java静态

    2024年02月12日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包