go语言-函数、方法和接口

语言: CN / TW / HK

Go语言中的函数有具名和匿名之分:具名函数一般对应于包级函数,是匿名函数的一种特例。当匿名函数引用了外部作用于中的变量时就成了闭包函数。方法是绑定到一个具体类型的特殊函数,Go语言中的方法依托于类型的,必须在编译时静态绑定。接口定义了方法的集合,这些方法依托于运行时的接口对象,因此接口对应的方法是在运行时动态绑定的。Go语言通过隐式接口机制实现了鸭子面向对象模型。

1.函数:

函数定义:函数大体由函数名,参数及返回值构成。

//具名函数
func Add(a, b int) int {
    return a + b
}

//匿名函数
var Add = func(a, b int) int {
    return a+b
}

Go语言中的函数可以有多个参数和多个返回值,参数和返回值都是以传值的方式和被调用者交换数据。另外函数还支持可变数量的参数,可变数量的参数必须是最后出现的参数,可变数量的参数其实是一个切片类型的参数。

//多个参数,多个返回值
func Swap(a, b int) (int, int) {
    return b, a
}

//可变数量的参数
//more 对应[]int 切片类型
func Sum(a int, more ...int) int {
    for _, v := range more {
        a += v
    }
    return a
}

当可变参数是一个空接口类型是,调用者是否解包可变参数会导致不同的结果:

func main() {
   var a = []interface{}{123, "abc"}
   Print(a...)    //123 abc
   Print(a)       //[123 abc]
}

func Print(a ...interface{}) {
   fmt.Println(a...)
}

第一个Print调用时传入的是a...,等价于直接调用Print(123, "abc")。第二个Print调用传入的是未解包的a,等价于直接调用Print([]interface{}{123, "abc"})。

不仅函数的参数可以有名字,也可以给函数的返回值命名。

func Find(m map[int]int, key int) (value int, ok bool) {
   value, ok = m[key]
   return 
}

如果返回值命名了,可以通过名字来修改返回值,也可以通过defer语句在return语句之后修改返回值:

func Inc (v int) int {
    defer func () { v++ } ()
    return v
}

其中defer语句延迟执行了一个匿名函数,因为这个匿名函数捕获了外部函数的局部变量v,这种函数我们一般称为“闭包”。闭包对捕获外部变量并不是以传值的方式访问,而是以引用的方式访问。

闭包的这种以饮用方式访问外部变量的行为可能会导致一些隐含的问题。

func main() {
   for i:=0;i<3;i++{
      defer func() {println(i)}()
   }
}
//Output
//3
//3
//3

因为是闭包,在for迭代语句中,每个defer语句延迟执行的函数引用的都是同一个i迭代变量,再循环结束后这个变量的值为3,因此最终输出的都是3。

修复的思路是在每轮迭代中为每个defer语句的闭包函数生成独有的变量。

func main() {
   for i:=0;i<3;i++{
      i := i
      defer func() { println(i) } ()
   }
}

//或者

func main() {
   for i:=0;i<3;i++{
      //通过函数传入i
      //defer 语句会马上对调用参数求值
      defer func(i int) { println(i) } (i)
   }
}

此处仅为示例,不建议在for循环中使用defer

2.接口

Go的接口类型是对其他类型行为的抽象和概括,因为接口类型不会和特定的实现细节绑定在一起,通过这种抽象的方式我们可以让对象更加灵活,更有适应能力。接口类型是延迟绑定,可以实现类似虚函数的多态功能。

Go语言中,基础类型(非接口类型)不支持隐式转换,我们无法将一个int类型的值直接赋值给int64类型的变量。但是Go语言对于接口类型的转换则非常灵活。对象和接口之间的转换、接口和接口的转换都可能是隐式的转换。

var (
    a io.ReadCloser = (*os.File)(f)  //隐式转换,*os.File满足io.ReadCloser接口
    b io.Reader = a                  //隐式转换,io.ReaderCloser满足io.Reader接口
    c io.Closer = a                  //隐式转换,io.ReaderCloser满足io.Closer接口
    d io.Reader = c.(io.Reader)      //显式转换,io.Cloder不满足io.Reader接口
)

我们可以通过嵌入匿名接口或嵌入匿名指针对象来实现纯虚集成,继承的只是接口指定的规范,真正的实现在运行的时候才被注入。例如,可以实现一个gRPC的插件:

type grpcPlugin struct {
    *generator.Generator
}

func (p *grpcPlugin) Name() string { return "grpc" }

func (p *grpcPlugin) Init(g *generator.Generator) {
    p.Generator = g
}

func (p *grpcPlugin) GenerateImports(file *generator.FileDescription) {
    if len(file.Service) == 0 {
        return
    }
    p.P('import "google.golang.org/grpc"')
}

构造的grpcPlugin类型对象必须满足generate.Plugin接口:

type Plugin interface {
    //Name identifies the plugin.
    Name() string
    //Init is called once after data structures are built but before
    //code generation begins
    Init(g *Generator)
    //Generate produce the code generated by the plugin for this file,
    //except for the imports, by calling the generator's methods
    //P, In ,and Out.
    Generate(file *FileDescriptor)
    //GenerateImports produces the import declarations for this file.
    //It is called after Generate.
    GenerateImports(file *FileDescripor)
}

generate.Plugin 接口对应的grpcPlugin类型的GenerateImports()方法中使用的p.P(...)函数,却是通过Init()函数注入的generator.Generator对象实现。

分享到: