广告位联系
返回顶部
分享到

Go语言中的switch高级用法介绍

Golang 来源:互联网 作者:佚名 发布时间:2024-10-02 12:18:31 人浏览
摘要

最近翻开源代码的时候看到了一种很有意思的switch用法,分享一下。 注意这里讨论的不是typed switch,也就是case语句后面是类型的那种。 直接看代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

最近翻开源代码的时候看到了一种很有意思的switch用法,分享一下。

注意这里讨论的不是typed switch,也就是case语句后面是类型的那种。

直接看代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

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个星星不是大家乱点的。

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

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

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

  • 首先是switch true,期待有个case能求出true这个值;
  • 从上到下执行strings.HasPrefix,如果是false就往下到下一个case,如果是true就进入这个case的分支。

它等价于下面这段:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

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语句是差不多的)。

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

我们做个简化版的例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

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版本的汇编:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

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的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

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写法,形式如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • Golang Map简介以及底层原理介绍
    在Go语言中提供了map数据结构来存储键值对数据。map的数据类型为map[K]V,其中K为键的类型,V为值的类型。map的键类型必须支持==操作符,用
  • Go语言中的switch高级用法介绍
    最近翻开源代码的时候看到了一种很有意思的switch用法,分享一下。 注意这里讨论的不是typed switch,也就是case语句后面是类型的那种。 直
  • Go语言sync.Map介绍及使用场景
    在 Go 语言中,sync.Map是一个并发安全的映射结构,专门用于在高并发场景下处理键值对数据。它的并发安全是通过内部实现机制来保证的,
  • 怎么使用工具自动监测SSL证书有效期并发送提醒
    自从云厂商的免费ssl证书改成3个月,而且证书数量还是20个之后,自己网站的ssl证书就换成了其它免费方案。但是免费方案不会提醒证书过
  • Go语言字符串处理库strings包介绍

    Go语言字符串处理库strings包介绍
    Golang字符串处理库strings 在Go语言中,strings包是一个非常重要且功能丰富的标准库,它提供了一系列用于操作字符串的函数。从基本的字符串
  • Go语言中的指针的使用介绍
    1、Go 语言中指针的介绍 1.1、什么是指针? 指针是一种变量,它存储了另一个变量的内存地址。在 Go 中,你可以通过取地址操作符获取变量
  • Go环境变量配置,及GOROOT、GOPATH的区别

    Go环境变量配置,及GOROOT、GOPATH的区别
    一、安装Go go下载地址: https://golang.google.cn/dl/ windows下载安装,有两种方式。解压和直接安装 方式一:直接下载安装包。以.msi结尾的文件。
  • golang interface指针实现
    在 Go 语言中,接口(interface)是一种类型,它定义了一组方法的集合。任何实现了接口中所有方法的类型都会自动满足该接口。当涉及到指针
  • Go语言等待组sync.WaitGrou的使用
    Go语言中除了可以使用通道(channel)和互斥锁进行两个并发程序间的同步外,还可以使用等待组进行多个任务的同步,等待组可以保证在并
  • Go语言中的接口类型介绍
    接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。 1.接口类型 1.1 接口类型的说明 Go语言中
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计