一、流程控制
if
if err := Connect(); err != nil {
fmt.Println(err)
return
}
判断表达式不再需要括号括起来
for
for 初始语句;条件表达式;结束语句{
循环体代码
}
初始语句和结束语句可省略
几种特殊写法:
for i <= 10 {
i++
}
for {
//do something
}
for ;;i++ {
//do something
}
for range
- 遍历数组、切片——获得索引和元素
for key, value := range []int{1, 2, 3, 4} {
fmt.Printf("key:%d value:%d\n", key, value)
}
- 遍历字符串——获得字符
var str = "hello 你好"
for key, value := range str {
fmt.Printf("key:%d value:0x%x\n", key, value)
}
- 遍历map——获得map的键和值
m := map[string]int{
"hello": 100,
"world": 200,
}
for key, value := range m {
fmt.Println(key, value)
}
- 遍历通道(channel)——接收通道数据
c := make(chan int)
go func() {
c <- 1
c <- 2
c <- 3
close(c)
}()
for v := range c {
fmt.Println(v)
}
代码说明如下:
第 1 行创建了一个整型类型的通道。
第 3 行启动了一个 goroutine,其逻辑的实现体现在第 5~8 行,实现功能是往通道中推送数据 1、2、3,然后结束并关闭通道。
这段 goroutine 在声明结束后,在第 9 行马上被并行执行。
从第 11 行开始,使用 for range 对通道 c 进行遍历,其实就是不断地从通道中取数据,直到通道被关闭。
- 在遍历中选择希望获得的变量
m := map[string]int{
"hello": 100,
"world": 200,
}
for _, value := range m {
fmt.Println(value)
}
switch/case
与c的区别是
1.switch 后面不再一定要一个整型变量,字符型的或者不写都可以;
2.case后面也不再要求是个整型常量,字符型的或者是一个判断语句都可以
3.不再需要break退出,自动默认推出
4.fallthrough兼容c语言里的continue;但是不建议使用
var a = "mum"
switch a {
case "mum", "daddy":
fmt.Println("family")
}
var r int = 11
switch {
case r > 10 && r < 20:
fmt.Println(r)
}
goto
goto跳到指定标签位置
err := firstCheckError()
if err != nil {
goto onExit
}
err = secondCheckError()
if err != nil {
goto onExit
}
fmt.Println("done")
return
onExit:
fmt.Println(err)
exitProcess()
break/continue
1.break 语句可以结束 for、switch 和 select 的代码块;
2.break 语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的 for、switch 和 select 的代码块上
3.continue也有类似2的操作,可以继续循环指定标签的loop
package main
import "fmt"
func main() {
OuterLoop:
for i := 0; i < 2; i++ {
for j := 0; j < 5; j++ {
switch j {
case 2:
fmt.Println(i, j)
break OuterLoop
case 3:
fmt.Println(i, j)
break OuterLoop
}
}
}
}
二、函数
函数定义
func 函数名(参数列表)(返回参数列表){
函数体
}
- 简写
类型相同只需要写一个变量名
func add(a, b int) int {
return a + b
}
- 返回值
函数返回值类型如果定义了变量名,函数体里可以不定义直接用,return时也不需要写完整的return语句
func typedTwoValues() (int, int) {
return 1, 2
}
a, b := typedTwoValues()
fmt.Println(a, b)
func namedRetValues() (a, b int) {
a = 1
b = 2
return
}
func namedRetValues() (a, b int) {
a = 1
return a, 2
}
- 调用
result := add(1,1)
把函数作为值进行保存
package main
import (
"fmt"
)
func fire() {
fmt.Println("fire")
}
func main() {
var f func()
f = fire
f() //输出fire
}
匿名函数
func(参数列表)(返回参数列表){
函数体
}
func(data int) {
fmt.Println("hello", data)
}(100) //定义后直接执行
// 将匿名函数体保存到f()中
f := func(data int) {
fmt.Println("hello", data)
}
// 使用f()调用
f(100)
- 匿名函数用作回调函数
package main
import (
"fmt"
)
// 遍历切片的每个元素, 通过给定函数进行元素访问
func visit(list []int, f func(int)) {
for _, v := range list {
f(v)
}
}
func main() {
// 使用匿名函数打印切片内容
visit([]int{1, 2, 3, 4}, func(v int) {
fmt.Println(v)
})
}
- 使用匿名函数实现封装操作
package main
import (
"flag"
"fmt"
)
var skillParam = flag.String("skill", "", "skill to perform")
func main() {
flag.Parse()
var skill = map[string]func(){
"fire": func() {
fmt.Println("chicken fire")
},
"run": func() {
fmt.Println("soldier run")
},
"fly": func() {
fmt.Println("angel fly")
},
}
if f, ok := skill[*skillParam]; ok {
f()
} else {
fmt.Println("skill not found")
}
}
可变参数
func 函数名(固定参数列表, v … T)(返回参数列表){
函数体
}
以下是对可变参数的函数的说明:
- 可变参数一般被放置在函数列表的末尾,前面是固定参数列表,当没有固定参数时,所有变量就将是可变参数。
- v 为可变参数变量,类型为 []T,也就是拥有多个 T 元素的 T 类型切片,v 和 T 之间由…即3个点组成。
- T 为可变参数的类型,当 T 为 interface{} 时,传入的可以是任意类型。
可变参数传递:
package main
import "fmt"
// 实际打印的函数
func rawPrint(rawList ...interface{}) {
// 遍历可变参数切片
for _, a := range rawList {
// 打印参数
fmt.Println(a)
}
}
// 打印函数封装
func print(slist ...interface{}) {
// 将slist可变参数切片完整传递给下一个函数
rawPrint(slist...)
}
func main() {
print(1, 2, 3)
}
延迟执行语句defer
Go 语言的 defer 修饰的语句,会在函数执行结束后,根据defer的顺序弹栈执行
package main
import (
"fmt"
)
func main() {
fmt.Println("defer begin")
// 将defer放入延迟调用栈
defer fmt.Println(1)
defer fmt.Println(2)
// 最后一个放入, 位于栈顶, 最先调用
defer fmt.Println(3)
fmt.Println("defer end")
}
执行结果:
defer begin
defer end
3
2
1
- 用defer实现并发控制
传统实现方法
var (
// 一个演示用的映射
valueByKey = make(map[string]int)
// 保证使用映射时的并发安全的互斥锁
valueByKeyGuard sync.Mutex
)
// 根据键读取值
func readValue(key string) int {
// 对共享资源加锁
valueByKeyGuard.Lock()
// 取值
v := valueByKey[key]
// 对共享资源解锁
valueByKeyGuard.Unlock()
// 返回值
return v
}
用defer实现
func readValue(key string) int {
valueByKeyGuard.Lock()
// defer后面的语句不会马上调用, 而是延迟到函数结束时调用
defer valueByKeyGuard.Unlock()
return valueByKey[key]
}
一般defer修饰的语句用来存放一些释放资源的操作,例如打开关闭文件之类的操作
运行时错误处理
- net包中的错误处理机制
func Dial(network, address string) (Conn, error) {
var d Dialer
return d.Dial(network, address)
}
在 io 包中的 Writer 接口也拥有错误返回,代码如下:
type Writer interface {
Write(p []byte) (n int, err error)
}
io 包中还有 Closer 接口,只有一个错误返回,代码如下:
纯文本复制
type Closer interface {
Close() error
}
- 自定义错误
错误接口
type error interface {
Error() string
}
错误类型
var err = errors.New("this is an error")
示例:
package main
import (
"fmt"
)
// 声明一个解析错误
type ParseError struct {
Filename string // 文件名
Line int // 行号
}
// 实现error接口,返回错误描述
func (e *ParseError) Error() string {
return fmt.Sprintf("%s:%d", e.Filename, e.Line)
}
// 创建一些解析错误
func newParseError(filename string, line int) error {
return &ParseError{filename, line}
}
func main() {
var e error
// 创建一个错误实例,包含文件名和行号
e = newParseError("main.go", 1)
// 通过error接口查看错误描述
fmt.Println(e.Error())
// 根据错误接口具体的类型,获取详细错误信息
switch detail := e.(type) {
case *ParseError: // 这是一个解析错误
fmt.Printf("Filename: %s Line: %d\n", detail.Filename, detail.Line)
default: // 其他类型的错误
fmt.Println("other error")
}
}
宕机处理 (panic)
package main
func main() {
panic("crash")
}
宕机恢复(recover)
Go 没有异常系统,其使用 panic 触发宕机类似于其他语言的抛出异常,那么 recover 的宕机恢复机制就对应 try/catch 机制。
package main
import (
"fmt"
"runtime"
)
// 崩溃时需要传递的上下文信息
type panicContext struct {
function string // 所在函数
}
// 保护方式允许一个函数
func ProtectRun(entry func()) {
// 延迟处理的函数
defer func() {
// 发生宕机时,获取panic传递的上下文并打印
err := recover()
switch err.(type) {
case runtime.Error: // 运行时错误
fmt.Println("runtime error:", err)
default: // 非运行时错误
fmt.Println("error:", err)
}
}()
entry()
}
func main() {
fmt.Println("运行前")
// 允许一段手动触发的错误
ProtectRun(func() {
fmt.Println("手动宕机前")
// 使用panic传递上下文
panic(&panicContext{
"手动触发panic",
})
fmt.Println("手动宕机后")
})
// 故意造成空指针访问错误
ProtectRun(func() {
fmt.Println("赋值宕机前")
var a *int
*a = 1
fmt.Println("赋值宕机后")
})
fmt.Println("运行后")
}
- panic和recover的关系
1.有 panic 没 recover,程序宕机。
2.有 panic 也有 recover 捕获,程序不会宕机。执行完对应的 defer 后,从宕机点退出当前函数后继续执行。