11. 函数
函数 是 Go 的中心。
package main
import (
"fmt"
)
//这里是一个函数,接受两个 int 并且以 int 返回它们的和
func plus(a int, b int) int {
//Go 需要明确的返回值,例如,它不会自动返回最后一个表达式的值
return a + b
}
func main() {
//通过 name(args) 来调用一个函数
res := plus(1, 2)
fmt.Println("1+2 =", res)
}
执行结果如下图所示:
这里有许多 Go 函数的其他特性。其中一个就是多值返回
12. 多返回值
Go 内建多返回值支持。这个特性在 Go 语言中经常被用到,例如用来同时返回一个函数的结果和错误信息。
package main
import (
"fmt"
)
//(int, int) 在这个函数中标志着这个函数返回 2 个 int
func vals() (int, int) {
return 3, 7
}
func main() {
//通过多赋值 操作来使用这两个不同的返回值。
a, b := vals()
fmt.Println(a)
fmt.Println(b)
//如果想返回值的一部分的话,你可以使用空白定义符 _。
_, c := vals()
fmt.Println(c)
}
执行结果如下图所示:
13. 变参函数
可变参数函数。可以用任意数量的参数调用。例如,fmt.Println 是一个常见的变参函数。
package main
import (
"fmt"
)
//这个函数使用任意数目 的 int 作为参数。
func sum(nums ...int) {
fmt.Print(nums, " ")
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total)
}
func main() {
//变参函数使用常规的调用方式,除了参数比较特殊
sum(1, 2)
sum(1, 2, 3)
//如果你的 slice 已经有了多个值,想把它们作为变参使用,你要这样调用 func(slice...)。
nums := []int{1, 2, 3, 4}
sum(nums...)
}
执行结果如下图所示:
14. 闭包
Go 支持通过 闭包来使用 匿名函数。匿名函数在你想定义一个不需要命名的内联函数时是很实用的。
package main
import (
"fmt"
)
//这个 intSeq 函数返回另一个在 intSeq 函数体内定义的匿名函数。
// 这个返回的函数使用闭包的方式 隐藏 变量 i
func intSeq() func() int {
i := 0
return func() int {
i += 1
return i
}
}
func main() {
//我们调用 intSeq 函数,将返回值(也是一个函数)赋给nextInt。
// 这个函数的值包含了自己的值 i,这样在每次调用 nextInt 时都会更新 i 的值。
nextInt := intSeq()
//通过多次调用 nextInt 来看看闭包的效果。
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
//为了确认这个状态对于这个特定的函数是唯一的,我们重新创建并测试一下。
newInts := intSeq()
fmt.Println(newInts())
}
执行结果如下图所示:
15. 递归
Go 支持 递归。这里是一个经典的阶乘示例。
package main
import (
"fmt"
)
//face 函数在到达 face(0) 前一直调用自身。
func fact(n int) int {
if n == 0 {
return 1
}
return n * fact(n-1)
}
func main() {
fmt.Println(fact(5))
}
执行结果如下图所示:
16. 指针
Go 支持 指针,允许在程序中通过引用 传递 值或者数据结构。
package main
import (
"fmt"
)
//我们将通过两个函数:zeroval 和 zeroptr 来比较指针和值类型的不同。
// zeroval 有一个 int 型参数,所以使用值传递。
// zeroval 将从调用它的那个函数中得到一个 ival形参的拷贝。
func zeroval(ival int) {
ival = 0
}
//zeroptr 有一和上面不同的 *int 参数,意味着它用了一个 int指针。
// 函数体内的 *iptr 接着解引用 这个指针,从它内存地址得到这个地址对应的当前值。
// 对一个解引用的指针赋值将会改变这个指针引用的真实地址的值。
func zeroptr(iptr *int) {
*iptr = 0
}
func main() {
i := 1
fmt.Println("initial:", i)
zeroval(i)
fmt.Println("zeroval:", i)
//通过 &i 语法来取得 i 的内存地址,例如一个变量i 的指针。
zeroptr(&i)
fmt.Println("zeroptr:", i)
//指针也是可以被打印的。
fmt.Println("pointer:", &i)
}
执行结果如下图所示:
zeroval 在 main 函数中不能改变 i 的值,但是zeroptr 可以,因为它有一个这个变量的内存地址的引用。
17. 结构体
Go 的结构体 是各个字段字段的类型的集合。这在组织数据时非常有用。
package main
import (
"fmt"
)
//这里的 person 结构体包含了 name 和 age 两个字段
type person struct {
name string
age int
}
func main() {
//使用这个语法创建了一个新的结构体元素
fmt.Println(person{"Bob", 20})
//你可以在初始化一个结构体元素时指定字段名字。
fmt.Println(person{name: "Alice", age: 30})
//省略的字段将被初始化为零值。
fmt.Println(person{name: "Fred"})
//& 前缀生成一个结构体指针。
fmt.Println(&person{name: "Ann", age: 40})
//使用点来访问结构体字段。
s := person{name: "Sean", age: 50}
fmt.Println(s.name)
fmt.Println(s.age)
//也可以对结构体指针使用. - 指针会被自动解引用。
sp := &s
fmt.Println(sp.age)
//结构体是可变的。
sp.age = 51
fmt.Println(sp.age)
}
执行结果如下图所示:
18. 方法
Go 支持在结构体类型中定义方法 。
package main
import (
"fmt"
)
type rect struct {
width, height int
}
//这里的 area 方法有一个接收器类型 rect。算矩形的面积
func (r *rect) area() int {
return r.width * r.height
}
//可以为值类型或者指针类型的接收器定义方法。这里是一个值类型接收器的例子。
//计算矩形的周长
func (r rect) perim() int {
return 2*r.width + 2*r.height
}
func main() {
r := rect{width: 10, height: 5}
//这里我们调用上面为结构体定义的两个方法。
fmt.Println("area: ", r.area())
fmt.Println("perim:", r.perim())
//Go 自动处理方法调用时的值和指针之间的转化。
//可以使用指针来调用方法来避免在方法调用时产生一个拷贝,或者让方法能够改变接受的数据。
rp := &r
fmt.Println("area: ", rp.area())
fmt.Println("perim:", rp.perim())
}
执行结果如下图所示:
19. 接口
接口 : Go 语言中组织和命名相关的方法集合的机制。接口 是方法特征的命名集合。
package main
import (
"fmt"
"math"
)
//这里是一个几何体的基本接口
type geometry interface {
area() float64
perim() float64
}
//在我们的例子中,我们将让 rect 和 circle 实现这个接口
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
//要在 Go 中实现一个接口,我们只需要实现接口中的所有方法。
// 这里我们让 rect 实现了 geometry 接口。
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
//circle 的实现。
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
//如果一个变量的是接口类型,那么我们可以调用这个被命名的接口中的方法。
//这里有一个一通用的 measure 函数,利用这个特性,它可以用在任何 geometry(几何学) 上。
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
//结构体类型 circle 和 rect 都实现了 geometry接口,
// 所以我们可以使用它们的实例作为 measure 的参数。
measure(r)
measure(c)
}
执行结果如下图所示:
20. 错误处理
Go 语言使用一个独立的·明确的返回值来传递错误信息的。这与使用异常的 Java 和 Ruby 以及在 C 语言中经常见到的超重的单返回值/错误值相比,Go 语言的处理方式能清楚的知道哪个函数返回了错误,并能像调用那些没有出错的函数一样调用。
package main
import (
"fmt"
"errors"
)
//按照惯例,错误通常是最后一个返回值并且是 error 类型,一个内建的接口。
func f1(arg int) (int, error) {
if arg == 42 {
//errors.New 构造一个使用给定的错误信息的基本error 值。
return -1, errors.New("can't work with 42")
}
//返回错误值为 nil 代表没有错误。
return arg + 3, nil
}
//通过实现 Error 方法来自定义 error 类型是可以的。
// 这里使用自定义错误类型来表示上面的参数错误。
type argError struct {
arg int
prob string
}
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
func f2(arg int) (int, error) {
if arg == 42 {
//在这个例子中,我们使用 &argError 语法来建立一个新的结构体,
// 并提供了 arg 和 prob 这个两个字段的值。
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}
func main() {
//下面的两个循环测试了各个返回错误的函数。
// 注意在 if行内的错误检查代码,在 Go 中是一个普遍的用法。
for _, i := range []int{7, 42} {
if r, e := f1(i); e != nil {
fmt.Println("f1 failed:", e)
} else {
fmt.Println("f1 worked:", r)
}
}
for _, i := range []int{7, 42} {
if r, e := f2(i); e != nil {
fmt.Println("f2 failed:", e)
} else {
fmt.Println("f2 worked:", r)
}
}
//你如果想在程序中使用一个自定义错误类型中的数据,
// 你需要通过类型断言来得到这个错误类型的实例。
_, e := f2(42)
if ae, ok := e.(*argError); ok {
fmt.Println(ae.arg)
fmt.Println(ae.prob)
}
}
执行结果如下图所示:
上一篇:学习Go语言必备案例 (1)
下一篇:学习Go语言必备案例 (3)