四时宝库

程序员的知识宝库

Go语言进阶之路(三):函数和接口(go语言调用c接口)

上一期我们说到了《Go语言进阶之路(二):字符串和指针》,这一期来聊一下Go语言的函数和接口,看看它和Java、Python有什么异同点。

一 函数

Go语言的函数用关键字func来定义,函数可以有返回值也可以没有返回值,有返回值的话,返回值写在函数参数的后面:

// 没有返回值的函数,函数参数是int类型的a和b
func myFunc(a, b int){  // a和b参数同类型
    xxx
}

带返回值的函数,还可以给返回值命名:

func change(a, b int) (x, y int) {
    x = a + 100
    y = b + 100
    return   //101, 102
    //return x, y  //同上
    //return y, x  //102, 101
}

func main(){
    a := 1
    b := 2
    c, d := change(a, b)
    println(c, d)
}

可变参数函数

Go语言支持可变参数函数,和Java中一样,都是用三个点...来表示参数个数是可变的,可以看下面的例子。另外,可变参数的类型其实是切片,跟Java一样,可变参数其实就是个语法糖。

func sum(nums ...int) {
  fmt.Println(reflect.TypeOf(nums))  // 输出:[]int
  fmt.Println(nums, " ")
  total := 0
  for _, num := range nums {
    total += num
  }
  fmt.Println(total)
}
func main() {
  sum(1, 2)               // [1 2] 3
  sum(1, 2, 3)            // [1 2 3] 6
  nums := []int{1, 2, 3, 4}
  sum(nums...)            // [1 2 3 4] 10
}

方法

一般的,我们把普通定义的函数叫做函数,定义给结构体的函数叫做方法。比如:

func func1(a, b int) int {  // 函数
    xxx
}


type Animal struct {
  name string
  age int
}

func (animai Animal) func2(a string, b int) (string, int) {    // 方法
    xxx
}

这里我们就把func1称作函数,func2称作方法。

匿名函数

Go语言支持匿名函数,和JavaScript中一样,我们可以把函数赋值给一个变量,也可以直接创建一个匿名函数立即执行。

我们把上面的可变参数函数例子改一下,把函数赋值给变量func1,然后调用func1函数:

var func1 = func(nums ...int) {
  fmt.Println(reflect.TypeOf(nums))  // 输出:[]int
  fmt.Println(nums, " ")
  total := 0
  for _, num := range nums {
    total += num
  }
  fmt.Println(total)
}
func main() {
  func1(1, 2)               // [1 2] 3
  func1(1, 2, 3)            // [1 2 3] 6
  nums := []int{1, 2, 3, 4}
  func1(nums...)            // [1 2 3 4] 10
}

我们创建一个匿名函数立即执行:

func(nums ...int) {
  fmt.Println(reflect.TypeOf(nums))  // 输出:[]int
  fmt.Println(nums, " ")  // 输出:[1 2 3]
  total := 0
  for _, num := range nums {
    total += num
  }
  fmt.Println(total) // 输出:6
}(1, 2, 3)

创建个匿名函数用于goroutine:

var jobs = make(chan int, 5)
var done = make(chan bool)

var func1 = func() {
  for {
    j, more := <-jobs        // 如果通道已被关闭,并且数据已全被取走,则more为false
    if more {
      fmt.Println("received job", j)
    } else {
      fmt.Println("received all jobs")
      done <- true
      return
    }
  }
}
func main() {
  go func1()  // 创建goroutine立即执行
}

后面会有专门的文章详解goroutine和通道。

二 接口

Go语言不是专门为面向对象设计的语言,但是Go语言也能实现面向对象的功能。在Go语言中,接口类型用interface关键字。比较特殊的是,Go语言中任何数据、任何对象都是interface类型的。Go语言中的interface类型就像Java中的Object类一样。

定义接口

type live interface{  // 定义一个接口
  eat()
  sleep()
}

定义一个表示“生活”的接口,有“吃”函数和“睡”函数。

实现接口

我们定义一个“人”类型,来实现“生活”接口:

type person struct {
  name string
  age int
}

func (p person) eat() {
  fmt.Println("eat something")
}

func (p *person) sleep() {
  fmt.Println("sleep sometimes")
}

可以看到,我们的person类型实现了eat函数和sleep函数。实现方法eat时候,我们把p person写在func关键字后面、eat方法前面,这样,我们就把p person作为了方法eat的接收者,换句话说,person类型实现了接口的eat函数。

当一个类型实现了一个接口的所有函数,那我们就叫做这个类型实现了这个接口。这样,我们创建一个person类型的变量,然后调用它的接口方法就可以了。

var p = person{}
p.eat()  // 输出:eat something
p.sleep()  // 输出:sleep sometimes

指针方法和值方法

等等,上面这个例子我们好像看到了点奇怪的东西,eat函数接收者是person类型,sleep函数接收者是指针类型。有什么区别?

从函数调用方式上看,这两者并没有什么区别。我们可以使用person类型和person的指针类型调用任意一个方法:

type person struct {
  name string
  age int
}
func (p person) eat() {
  fmt.Println("eat something")
}

func (p *person) sleep() {
  fmt.Println("sleep sometimes")
}
func main() {
  var p1 = person{}
  p1.eat()  // 输出:eat something
  p1.sleep()  // 输出:sleep sometimes
  var p2 = &person{}
  p2.eat()  // 输出:eat something
  p2.sleep()  // 输出:sleep sometimes
}

其实,区别在于,用指针类型作为接收者,可以在函数/方法内部修改这个接收者的数据,而用值类型作为接收者,在函数/方法内部不能修改原接收者的数据。看下面的例子就明白了:

type person struct {
  name string
  age int
}
func (p person) eat() {
  p.name = "eat"
  fmt.Println("eat something")
}

func (p *person) sleep() {
  p.age = 28
  fmt.Println("sleep sometimes")
}
func main() {
  var p1 = person{}
  fmt.Println(p1)  // 输出:{ 0}
  p1.eat()  // 输出:eat something
  p1.sleep()  // 输出:sleep sometimes
  fmt.Println(p1)  // 输出:{ 28}
  var p2 = &person{}
  fmt.Println(p2)  // 输出:&{ 0}
  p2.eat()  // 输出:eat something
  p2.sleep()  // 输出:sleep sometimes
  fmt.Println(p2)  // 输出:&{ 28}
}

可以看到,p2是指针类型,在调用值方法eat时,eat方法内部修改的name属性也没有反映到p2上面。其实,这和我们调用函数时,把切片作为函数参数一样。在调用函数/方法时,接收者对象也会被复制一份,然后再调用函数/方法。对于值类型,直接就复制了一份数据,所以修改不到原来的值类型接收者;对于指针类型,复制的是指针,因此我们可以在函数/方法内部修改到原接收者的数据。

多态

提到接口,当然要提到Java中非常流行的多态。Go语言也可以实现多态,看下面的例子:

type animal interface {
  eat()
  sleep()
}

type person struct {
  name string
  age  int
}

func (p person) eat() {
  fmt.Println("person eat something")
}

func (p person) sleep() {
  fmt.Println("person sleep sometimes")
}

type cat struct {
  name string
  age  int
}

func (p cat) eat() {
  fmt.Println("cat eat something")
}

func (p cat) sleep() {
  fmt.Println("cat sleep sometimes")
}
func main() {
  var a animal = person{"person", 28}
  a.eat()  // 输出:person eat something
  a.sleep()  // 输出:person sleep sometimes
  a = cat{"cat", 5}
  a.eat()  // 输出:cat eat something
  a.sleep()  // 输出:cat sleep sometimes
}

person类型和cat类型都实现了animal接口,创建animal类型变量a,把person类型变量和cat类型变量赋值给a,调用方法时,会多态执行到具体实现上。

空接口/任意类型接口

我们知道,interface{}类型就像Java里面的Object类一样,同时Go语言又具有多态的特性,那么我们可以使用空接口变量来存放任意类型数据。比如:

 var a interface{} = 2
 fmt.Println(a)  // 输出:2

我们来创建一个可以存放任意类型的map:

func main() {
 var m = make(map[string]interface{})
 m["a"] = 1
 m["b"] = "b"
 m["c"] = []int{1, 2}
 fmt.Println(m)  // 输出:map[a:1 b:b c:[1 2]]
}

下一期我们聊一下Go语言中的Go语言中的标准错误和异常。

喜欢的可以关注我的订阅号【程序猿架构】,更快的获取高质量文章

?

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接