Golang Label使用方法

2017-08-10
4分钟阅读时长

C/C++的时候, 基本上都是建议不要使用goto的, 因为goto运用的不好的话, 会改变程序的运行结构, 会造成程序运行的混乱. 所以很少能看到C++代码中有使用goto的.

不过看到Golang中, 不仅保留了goto, 而且把它发扬光大了.

在Golang中能使用Label的有goto, break, continue. 这篇文章就介绍下Golang中Label使用和注意点.

注意点:

  1. Label在continue, break中是可选的, 但是在goto中是必须的
  2. 作用范围: 定义Label的函数体内.
  3. Label可以声明在函数体的任何位置, 不管Label声明在调用点的前面还是后面.

看到国外一篇文章写的不错, 加上自己的一些理解, 写了这么一篇文章.

一. goto

下面就以goto为例子展示上面三点特点.

1.Labelgoto是必须的

package main

import (
	"fmt"
)

func main() {
	fmt.Println(1)
	goto End
	//goto 	10: syntax error: unexpected . at end of statement
	fmt.Println(2)
End:
	fmt.Println(3)
}

Output: 1 3

2.Label可以声明在函数体的任何地方

package main

import (
	"fmt"
)

func main() {
End:
	fmt.Println(1)
	goto End
	fmt.Println(2)
	fmt.Println(3)
}

Output 1 1 1 ….

3.Label的作用范围是在函数体中

package main

import (
	"fmt"
)

func main() {
	fmt.Println(1)
	goto End
	fmt.Println(2)
}

End:
	fmt.Println(3)

Output: syntax error: non-declaration statement outside function body

4.Label在嵌套函数(闭包)是不可用的. 不管是在闭包里调用闭包外的Label, 还是在闭包外调用闭包里的Label

package main

import (
	"fmt"
)

func main() {
	fmt.Println(1)
	func() {
		fmt.Println("Nested function")
		goto End
	}()
End:
	fmt.Println(2)
}

Output 11:label End not defined 13:label End defined and not used

5.不能重复定义Label

package main

import (
	"fmt"
)

func main() {
	fmt.Println(1)
	goto End
	
End
	fmt.Println(2)
	{
	End:
		fmt.Println(3)
	}
}

Output 14: label End already defined at ./label.go:11

6.Label和变量名是不冲突的, 可以定义一个名为x的变量和名为x的Label(不过不建议这么用, 这么写会被人骂的); 而且Label是区分大小写的.

package main

import (
	"fmt"
)

func main() {
	x := 1
	fmt.Println(x)
	goto x
x:
	fmt.Println(2)
}

Output: 1 2

7.变量的声明必须在goto之前.

package main

import (
	"fmt"
)

func main() {
	goto End
	j := 2
	fmt.Println(j)
End:
	fmt.Println(1)
}

Output goto End jumps over declaration of i at ./label.go:9

这是为什么呢? 因为任何变量的声明都不能被跳过.

需要改成下面的形式

package main

import (
	"fmt"
)

func main() {
	j := 2
	goto End
	fmt.Println(j)
End:
	fmt.Println(2)
}

二. break(不带label)

break一般用来跳出最近一层的switchfor, 注意不能用在select

1.单层循环

package main

import (
	"fmt"
)

func main() {
	for i := 0; i < 10; i++ {
		fmt.Println(i)
		if i == 3 {
			break
		}
	}
}

Output 0 1 2 3

2.双层循环

package main

import (
	"fmt"
)

func main() {
	for i := 0; i < 3; i++ {
		for j := 0; j < 5; j++ {
			fmt.Println("i:", i, ",j:", j)
			if j == 2 {
				break
			}
		}
	}
}

Output i: 0 ,j: 0 i: 0 ,j: 1 i: 0 ,j: 2 i: 1 ,j: 0 i: 1 ,j: 1 i: 1 ,j: 2 i: 2 ,j: 0 i: 2 ,j: 1 i: 2 ,j: 2

从这个例子可以看出break只能跳出最近for

3.对于c/c++来说, switch/case一般都是配合break来使用的.但是在golangswitch/case不需要break就能够实现和c/c++一样的效果.

package main

import (
	"fmt"
)

func main() {
	i := 1
	switch {
	case i == 0:
		fmt.Println(i)
	case i == 1:
		fmt.Println(i)
		//break 这里可以使用`break`,但是么有啥效果, 不如不写
	case i == 2:
		fmt.Println(i)
	}
}

Output 1

如果想继续往下执行, 需要使用fallthrough

package main

import (
	"fmt"
)

func main() {
	i := 1
	switch {
	case i == 0:
		fmt.Println(0)
	case i == 1:
		fmt.Println(1)
		fallthrough
	case i == 2:
		fmt.Println(2)
	}
}

Output: 1 2

4.break在函数里是不起作用的, 不能传递出来.

package main

func f() {
	break
}

func main() {
	for i := 0; i < 10; i++ {
		f()
	}
}

output 4: break is not in a loop

三.break(Label)

break携带label可以用在for,switch,select上.

1.对于for/select /switch ,Label必须紧挨着他们.

FirstLoop:
    for i := 0; i < 10; i++ { //invalid break label FirstLoop
    }
    for i := 0; i < 10; i++ {
        break FirstLoop
    }

必须改成这样

func main() {
	for i := 0; i < 10; i++ {
		fmt.Println(i)
	}
FirstLoop:
	for i := 0; i < 10; i++ {
		break FirstLoop
	}
}

对于selectswitch也是一样.

func main() {
FirstLoop:
	j := 1
	switch j { 
	case 0:
		fmt.Println(0)
	case 1:
		fmt.Println(1)
		break FirstLoop // invalid break label FirstLoop
	}
}

2.一般来说break只能跳出最近一层的for, switch, 但是break Label就可以直接跳出最外面的循环.

func main() {
OuterLoop:
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			fmt.Printf("i=%v, j=%v\n", i, j)
			break OuterLoop
		}
	}
}

Output i=0, j=0

SwitchStatement:
    switch 1 {
    case 1:
        fmt.Println(1)
        for i := 0; i < 10; i++ {
            break SwitchStatement
        }
        fmt.Println(2)
    }
    fmt.Println(3)

Output 1 3

四. continue

continue用法基本上和break差不多. 1.正常的用法, 调过当前循环, 继续执行下一次

package main

import (
	"fmt"
)

func main() {
	for i := 0; i < 5; i++ {
		if i == 3 {
			continue
		}
		fmt.Println(i)
	}
}

Output 0 1 2 4

2.continuelabel一起使用(其实和不使用Label效果一样)

func main() {
Test:
	for i := 0; i < 5; i++ {
		if i == 3 {
			continue Test
		}
		fmt.Println(i)
	}
}

Output 0 1 2 4

3.continue和双层循环一起使用

OuterLoop:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            fmt.Printf(“i=%v, j=%v\n”, i, j)
            continue OuterLoop
        }
    }

Output i=0, j=0 i=1, j=0 i=2, j=0

参考资料:

  1. Labels in Go
  2. Label Breaks In Go