上一期我们说到了《Go语言进阶之路(二):字符串和指针》,这一期来聊一下Go语言的函数和接口,看看它和Java、Python有什么异同点。
一 函数
Go语言的函数用关键字func来定义,函数可以有返回值也可以没有返回值,有返回值的话,返回值写在函数参数的后面:
// 没有返回值的函数,函数参数是int类型的a和bfunc 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 := // 如果通道已被关闭,并且数据已全被取走,则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 somethingp.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语言中的标准错误和异常。
喜欢的可以点个关注或者在看支持一下!
推荐阅读
Go语言进阶之路(二):字符串和指针
喜欢本文的朋友,欢迎关注“Go语言中文网”:
Go语言中文网启用微信学习交流群,欢迎加微信:274768166,投稿亦欢迎