Go编程基础(函数、方法和接口)

这篇讲的非常重要,涉及到函数、方法和接口。在实际项目中用到的非常多,注意go语言中的接口是没有继承关系的,但是可以利用接口嵌套的方式实现接口继承的功能。

1.函数function

函数function知识要点:

  • 1、Go函数不支持嵌套、重载和默认参数;
  • 2、但支持一下特性:
    • 1、无须声明原型、不定长度变参、多返回值、命令返回值参数;
    • 2、匿名函数、闭包
  • 3、定义函数使用关键字func,且左大括号不能另起一行,函数也可以作为一种类型使用

参数为不定长参数,相当于传递一个slice,如果修改参数slice中值会影响原slice的值吗?

package main
import "fmt"
func main() {
    a,b :=1,2
    B(a,b)
    fmt.Println(a,b)
}
//不定长变参的使用方法,注意这里的s相当于一个slice
//但是如果修改slice中的值会怎么呢?
func B(s ...int)  {
    s[0]=3
    s[1]=4
    fmt.Println(s)
}
image.png

注意这个传递的slice是一个值传递(slice拷贝),不会影响原slice的值。

如果传递一个slice又会怎么样呢?(内存地址的拷贝)

package main
import "fmt"
func main() {
    s1 :=[]int{1,2,3,4}
    B(s1)
    fmt.Println(s1)
}
//传递一个slice,这里的拷贝是内存地址,本质还是一个值传递
func B(s []int)  {
    s[0]=5
    s[1]=6
    s[2]=7
    s[3]=8
    fmt.Println(s)
}
image.png

指针传递(引用传递)

package main
import "fmt"
func main() {
    a:=1
    B(&a)
    fmt.Println(a)
}
//传递一个指针
func B(a *int)  {
    *a =5
    fmt.Println(*a)
}

函数本身传递

package main
import "fmt"
func main() {
    //将B函数赋值给b,然后调用b
    b:=B
    b()
}
func B()  {
    fmt.Println("Func A")
}
image.png

匿名函数传递

package main
import "fmt"
func main() {
    //匿名函数的调用
    b:=func ()  {
        fmt.Println("Func A")
    }
    b()
}

闭包(closure)

package main
import "fmt"
func main() {
    f:=closure(10)
    fmt.Println(f(1))
    fmt.Println(f(2))
}
func closure(x int) func(int) int  {
    fmt.Printf("%p\n",&x)
    return func(y int) int {
        fmt.Printf("%p\n",&x)
        return x+y
    }
}
image.png

后面一篇博客会详细讲到闭包的作用。

2.方法method

方法method的使用注意事项:

  • 1、Go中虽没有class,但依旧有method通过显式说明Receiver来实现与某个类型的组合;只能为同一个包中定义类型定义方法。
  • 2、Receiver可以是类型的值或者指针;
  • 3、不存在方法重载问题;
  • 4、可以使用值或指针来调用方法,编译器会自动完成转换
  • 5、从某种意义上来说,方法是函数的语法糖,因为Receiver其实就是方法所接收的第一个参数(Method Value与);
  • 6、如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法;
  • 7、类型别名不会拥有底层类型所附带的方法;
  • 8、方法可以调用结构中的非公开字段。

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。

举例来说,许多程序语言提供专门的语法来对数组中的元素进行引用和更新。从理论上来讲,一个数组元素的引用涉及到两个参数:数组和下标向量,比如这样的表达式,get_array(Array, vector(i, j))。然而,许多语言支持这样直接引用 Array[i, j]。同理,数组元素的更新涉及到三个参数,set_array(Array, vector(i, j), value),但是很多语言提供这样直接赋值,Array[i, j] = value。

package main
import (
"fmt"
"math"
)
type Vertex struct {
    X, Y float64
}
//注意方法的定义,这里使用的指针传递参数
func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
    //初始化结构体
    v := &Vertex{3, 4}
    //利用指针调用方法
    fmt.Println(v.Abs())
}

3.接口interface

接口interface注意事项:

  • 1、接口是一个或多个方法签名的集合;
  • 2、只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显示声明实现了哪个接口,这称为Structural Typing
  • 3、接口只有方法声明,没有实现,没有数据字段;
  • 4、接口可以匿名嵌入其他接口,或嵌入到结构中;
  • 5、将对象赋值给接口时,会发生拷贝,而接口内存存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针
  • 6、只有当接口存储的类型和对象都为nil时,接口才等于nil
  • 7、接口调用不会做receiver的自动转换;接口同样支持匿名字段方法;接口也可实现类似OOP中的多态;空接口可以作为任何类型数据的容器。

接口的定义

package main
import "fmt"
//声明一个接口,里面有两个方法:Name和Connect方法
type USB interface {
    Name() string
    Connect()
}
//连接器结构体,这个类型实现了USB接口中所有方法(Name和Connect),所以PhoneConnector叫做实现了USB接口
type PhoneConnector struct {
    name string
}
func (pc PhoneConnector) Name() string  {
    return pc.name
}
func (pc PhoneConnector) Connect() {
    fmt.Println("Connect",pc.name)
}
func main() {
    //定义USB接口类型的变量
    //var a USB
    //初始化接口
    a:=PhoneConnector{"华为连接器"}
    a.Connect()
    Disconnect(a)
}
func Disconnect(usb USB) {
    fmt.Println("Disconnect")
}
image.png

嵌入接口

将上面改为嵌入接口格式为:

//声明一个接口,里面有一个Name()方法和Connecter接口
type USB interface {
    Name() string
    //将Connecter接口嵌入到USB接口中
    Connecter
}
type Connecter interface {
    Connect()
}

如何判断interface变量存储的是哪种类型(类型断言)

一个interface被多种类型实现时,有时候我们需要区分interface的变量究竟存储哪种类型的值,go可以使用comma,ok的形式做区分value,ok:=em.(T)eminterface类型的变量,T代表要断言的类型,valueinterface变量存储的值,okbool类型标识是否为该断言的类型T

//下面判断PhoneConnector是不是USB类型的
func Disconnect(usb USB) {
    if pc,ok :=usb.(PhoneConnector);ok{
        fmt.Println("Disconnect",pc.name)
        return
    }
    fmt.Println("Unknown devive")
}

空接口

interface{} 是一个空的 interface 类型,根据前文的定义:一个类型如果实现了一个 interface 的所有方法就说该类型实现了这个 interface,空的 interface 没有方法,所以可以认为所有的类型都实现了 interface{}。如果定义一个函数参数是 interface{} 类型,这个函数应该可以接受任何类型作为它的参数。

image.png

接口转换

接口只能有超接口转换子接口,相反则不行。

image.png
    //将USB接口转换为Connecter接口
    pc:=PhoneConnector{"华为连接器"}
    var a Connecter
    //转换动作
    a=Connecter(pc)
    a.Connect()

修改接口中的值,发生的是拷贝动作,不影响原来值

func main() {
    //将USB接口转换为Connecter接口
    pc:=PhoneConnector{"华为连接器"}
    var a Connecter
    a=Connecter(pc)
    a.Connect()

    //然后修改pc的name字段
    pc.name="pc"
    fmt.Println(pc.name)
    a.Connect()
}
image.png

参考资料

https://github.com/Unknwon/go-web-foundation

你可能感兴趣的:(Go编程基础(函数、方法和接口))