1.流程控制
1.1 条件语句
if a < 5 {
return 0
} else {
return 1
}
注意:在有返回值的函数中,不允许将“最终的”return语句包含在if...else...结构中, 否则会编译失败!!!
func example(x int) int {
if x == 0 {
return 5
} else {
return x
}
}
// 编译失败的原因:Go编译器无法找到终止该函数的return语句
1.2 选择语句
switch i {
case 0:
fmt.Printf("0")
case 1:
fmt.Printf("1")
case 2:
fallthrough
case 3:
fmt.Printf("3")
case 4, 5, 6:
fmt.Printf("4, 5, 6")
default:
fmt.Printf("Default")
}
运行上面的案例,将会得到如下结果: i = 0 时,输出 0 ; i = 1 时,输出 1 ; i = 2 时,输出 3 ; i = 3 时,输出 3 ; i = 4 时,输出 4, 5, 6 ; i = 5 时,输出 4, 5, 6 ; i = 6 时,输出 4, 5, 6 ; i = 其他任意值时,输出 Default 。
比较有意思的是,switch后面的表达式甚至不是必需的,比如下面的例子:
switch {
case 0 <= Num && Num <= 3:
fmt.Printf("0-3")
case 4 <= Num && Num <= 6:
fmt.Printf("4-6")
case 7 <= Num && Num <= 9:
fmt.Printf("7-9")
}
- 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case
- 可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个 if...else...的逻辑作用等同
- 在条件表达式中也支持多重赋值
func ForCase() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum) // 45
// 在条件表达式中也支持多重赋值,如下表示:
a := []int{1, 2, 3, 4, 5, 6}
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
fmt.Println(a) // [6 5 4 3 2 1]
}
另外,Go语言的for循环同样支持continue和break来控制循环,但是它提供了一个更加高级的break,可以选择中断哪一个循环,如下例子:
for j := 0; j < 5; j++ {
for i := 0; i < 10; i++ {
if i > 5 {
break JLoop
}
fmt.Println(i)
}
}
JLoop:
- 在Go中,函数的基本组成为:关键字func、函数名、参数列表、返回值、函数体和返回语句
package main
import (
"fmt"
)
import "errors"
func Add(a int, b int) (ret int, err error) {
if a < 0 || b < 0 { // 假设这个函数只支持两个非负数字的加法
err = errors.New("Should be non-negative numbers!")
return
}
return a + b, nil // 支持多重返回值
}
func main() {
res, err := _case.Add(1, 2)
if err != nil {
return
}
fmt.Println(res)
}
func Add(a, b int)(ret int, err error) {
// ...
}
func Add(a, b int) int {
// ...
}
2.2 函数调用
import "mymath"// 假设Add被放在一个叫mymath的包中
// ...
c := mymath.Add(1, 2)
- 小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用
- 这个规则也适用于类型和变量的可见性
func myfunc(args ...int) {
for _, arg := range args {
fmt.Println(arg)
}
}
// 这段代码的意思是,函数myfunc()接受不定数量的参数,这些参数的类型全部是int,
// 所以它可以用如下方式调用:
myfunc(2, 3, 4)
myfunc(1, 3, 7, 13)
形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数
它是一 个语法糖(syntactic sugar),即这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说,使用语法糖能够增加程序的可读性,从而减少程序出错的机会。
从内部实现机理来说说,类型...type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数args可以用for循环来获得每个传入的参数
假如没有...type这样的语法糖,开发者将不得不这么写:
func myfunc2(args []int) {
for _, arg := range args {
fmt.Println(arg)
}
}
myfunc2([]int{1, 3, 7, 13})
package main
import (
"fmt"
)
func processFunc(args ...int) {
for i, _ := range args {
args[i] += 1
}
fmt.Println(args)
}
func myfunc(args ...int) {
// 按照原样传递
processFunc(args...)
// 传递片段,实际上任意的int slice都可以传进去
processFunc(args[1:]...)
}
func main() {
myfunc(2, 3, 4)
}
(3)任意类型的不定参数
func Printf(format string, args ...interface{}) {
// ...
}
用interface{}传递任意类型数据是Go语言的惯例用法。使用interface{}仍然是类型安全的,和C/C++不太一样
func MyPrintf(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "is an int value.")
case string:
fmt.Println(arg, "is a string value.")
case int64:
fmt.Println(arg, "is an int64 value.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
func main() {
var v1 int = 1
var v2 int64 = 234
var v3 string = "heheda"
var v4 float32 = 1.234
_case.MyPrintf(v1, v2, v3, v4)
}
(4)多返回值
与C、C++和Java等开发语言的一个极大不同在于,Go语言的函数或者成员的方法可以有多个返回值,这个特性能够让我们写出比其他语言更优雅,更简洁的代码。
比如File.Read()函数就可以同时返回读取的字节数和错误信息
如果读取文件成功,则返回值中的n为读取的字节数,err为nil,否则err为具体的出错信息:
func (file *File) Read(b []byte) (n int, err Error)
同样,从上面的方法原型可以看到,我们还可以给返回值命名,就像函数的输入参数一样。
- 返回值被命名之后,它们的值在函数开始的时候被自动初始化为空
- 在函数中执行不带任何参数的return语句时,会返回对应的返回值变量的值
Go语言并不需要强制命名返回值,但是命名后的返回值可以让代码更清晰,可读性更强,同时也可用于文档
如果调用方调用了一个具有多返回值的方法,但是却不想关心其中的某个返回值,可以简单地用一个下划线"_"来跳过这个返回值,比如下面的代码表示调用者在读文件的时候不想关心Read()函数返回的错误码:
n, _ := f.Read(buf)
(5)匿名函数与闭包
① 匿名函数是指不需要定义函数名的一种函数实现方式,匿名函数由一个不带函数名的函数声明和函数体组成,如下所示:
func NMFunc() {
f := func(x, y int) int {
return x + y
}
fmt.Println(f(1, 2)) // 输出: 3
// 创建一个通道
replyChan := make(chan int)
// 启动一个新的 Goroutine,执行匿名函数,并向通道发送数据
go func(ch chan int) {
ch <- 42
}(replyChan) // 花括号后直接跟参数列表,表示调用
// 从通道接收数据
ack := <-replyChan
// 打印接收到的数据
fmt.Println(ack) // 输出: 42
// 关闭通道
close(replyChan)
}
②闭包:Go的匿名函数是一个闭包
基本概念:闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含 在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。
闭包的价值:在于可以作为函数对象或者匿名函数
Go语言中的闭包:Go语言中的闭包同样会引用到函数外的变量,闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在
closure.go
package main
import (
"fmt"
)
func main() {
var j int = 5
a := func()(func()) {
var i int = 10
return func() {
fmt.Printf("i, j: %d, %d\n", i, j)
}
}()
a()
j *= 2
a()
}
上述例子的执行结果是:i, j: 10, 5i, j: 10, 10
type error interface {
Error() string
}
func Foo(param int)(n int, err error) {
// ...
}
n, err := Foo(0)
if err != nil {
// 错误处理
} else {
// 使用返回值n
}
自定义的error类型
步骤一:定义一个用于承载错误信息的类型
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
当然,这里是一个完整的例子展示了如何定义一个自定义的 PathError
类型,并在文件操作中使用这个类型在出错时进行错误处理和信息提取
package main
import (
"fmt"
"os"
)
// PathError 自定义错误类型
type PathError struct {
Op string
Path string
Err error
}
// 实现 error 接口的 Error 方法
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
// 函数用来模拟文件操作,并返回一个自定义的 PathError 错误
func simulateFileOperation() error {
// 尝试获取文件信息,这里故意对一个不存在的文件操作
_, err := os.Stat("a.txt")
if err != nil {
// 返回自定义的 PathError 错误
return &PathError{
Op: "stat",
Path: "a.txt",
Err: err,
}
}
return nil
}
func main() {
// 调用模拟文件操作的函数
err := simulateFileOperation()
if err != nil {
// 把错误断言为 *PathError 类型
if e, ok := err.(*PathError); ok && e.Err != nil {
// 获取 PathError 类型变量 e 中的其他信息并处理
fmt.Printf("Operation: %s\n", e.Op)
fmt.Printf("Path: %s\n", e.Path)
fmt.Printf("Error: %s\n", e.Err)
fmt.Printf("Full Error: %s\n", e.Error())
} else {
// 其他类型的错误
fmt.Println(err)
}
} else {
// 文件操作成功
fmt.Println("File operation succeeded")
}
}
在这个示例中:
① 定义了一个PathError类型,这个类型有Op(操作)、Path(路径)和Err(底层错误)三个字段
② 实现了PathError类型的Error方法,以满足error接口
③ 编写了一个simulateFileOperation函数,这个函数尝试获取一个不存在的文件的状态,并返回一个自定义的PathError错误
④ 在main函数中,调用simulateFileOperation函数,接收并处理这个自定义错误
这样,当你运行这个示例时,如果文件a.txt不存在,会输出:
Operation: stat
Path: a.txt
Error: stat a.txt: no such file or directory
Full Error: stat a.txt: stat a.txt: no such file or directory
这个示例展示了如何自定义错误类型并在程序中使用它来进行错误处理和信息提取
这就是Go中error类型的使用方法,与其他语言中的异常相比,Go的处理相对比较直观,简单
2.5 defer
- 关键字defer是Go语言引入的一个非常有意思的特性