青训营后端Go语言基础(六) | 豆包MarsCode AI 刷题

24 阅读10分钟

13.接口

go中的接口是一种抽象数据类型,go中接口定义了对象的行为规范,只定义规范不实现。接口中定义的规范由具体的对象来实现

通俗的讲接口就一个标准,它是对一个对象的行为和规范进行约定,约定实现接口的对象必须得按照接口的规范。

go接口的定义

  1. 在go中接口(interface)是一种类型,一种抽象的类型。接口(interface)是一组函数method的集合,go中的接口不能包含任何变量。
  2. 在go中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序设计的堕胎和高内聚低耦合的思想。
  3. go中的接口也是一种数据类型,不需要显示实现。只需要一个变量含有接口中的所有方法,那么这个变量就实现了这个接口

go中每个接口由数个方法组成,接口的定义格式如下:

type 接口名 interface{
    方法1(参数列表1)返回值列表1
    方法2(参数列表2)返回值列表2
    ...
}

其中:

  • 接口名: 使用type将接口定义为自定义的类型名。go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型定义
  • 方法名: 当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问
  • 参数列表,返回值列表: 参数列表和返回值列表中的参数变量名可以省略

演示:定义一个Usber接口让Phone和Camera结构体实现这个接口

package main
​
import "fmt"type Usber interface {
    start()
    stop()
}
​
//如果结构体里面有方法的话,必须要通过结构体或者通过自定义类型实现这个接口
type hone struct{}
type camera struct{}
​
//要实现usb接口的话必须要实现usb接口中的所有方法
func (p phone) start() {
    fmt.Println("手机启动。。。")
}
func (p phone) stop() {
    fmt.Println("手机关机。。。")
}
func (c camera) start() {
    fmt.Println("相机启动。。。")
}
func (c camera) stop() {
    fmt.Println("相机关机。。。")
}
func main() {
    var p = phone{}
    var c = camera{}
​
    var p1 Usber //go中接口就是一个数据类型
    var c1 Usber
    c1 = c
    p1 = p //表示手机实现usb接口
    p1.start()
    p1.stop()
    c1.start()
    c1.stop()
}

例题升级:

package main
​
import "fmt"type Usber interface {
    start()
    stop()
}
​
//如果结构体里面有方法的话,必须要通过结构体或者通过自定义类型实现这个接口
type phone struct{}
type camera struct{}
type computer struct{}
​
//要实现usb接口的话必须要实现usb接口中的所有方法
func (p phone) start() {
    fmt.Println("手机启动。。。")
}
func (p phone) stop() {
    fmt.Println("手机关机。。。")
}
func (c camera) start() {
    fmt.Println("相机启动。。。")
}
func (c camera) stop() {
    fmt.Println("相机关机。。。")
}
func (cp computer) work(usb Usber) {//升级地方
    usb.start()
    usb.stop()
}
func main() {
    var p = phone{}
    var c = camera{}
    var cp = computer{}
    cp.work(p)
    cp.work(c)
}
​

空接口

go中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。空接口表示没有任何约束,因此任何类型变量都可以实现空接口。

空接口在实际项目中用的是非常多的,用空接口可以表示任意数据类型。

案例1

type A interface{}
func main(){
    var a A
    var str = "你好golang"
    a=str//让字符串实现A这个接口
    fmt.Printf("值:%v 类型:%T",a,a)//值:你好golang 类型:string
}

案例2:go中空接口也可以直接当作类型来使用,可以表示任意类型

func main(){
    var a interface{}
    a=20
    fmt.Printf("值:%v 类型:%T",a,a)//值:20 类型:int
    a="你好golang"
    fmt.Printf("值:%v 类型:%T",a,a)//值:你好golang 类型:string
    a=true
    fmt.Printf("值:%v 类型:%T",a,a)//值:true 类型:bool
}

案例3:空接口作为函数的参数

使用空接口实现可以接受任意类型的函数参数

func show(a interface{}){
    fmt.Printf("值:%v 类型:%T\n",a,a)
}
func main(){
    show(20)//值:20 类型:int
    show("你好golang")//你好golang 类型:string
    slice:=[]{1,2,3}//值:{1,2,3} 类型:slice
}

案例4:map的值实现空接口

使用空接口可以实现保存任意值的字典

func main(){
    var m1 = make(map[string]interface{})
    m1["username"]="张三"
    m1["age"]=20
    m1["married"]=false
    fmt.Println(m1)//map[age:20  username:张三 married:true]
}

案例4:切片实现空接口

func main(){
    var s1=[]interface{1,2,"你好",true}
    fmt.Println(s1)//[1,2,"你好",true]
}

类型断言

一个接口的值(简称接口值)是由一个具体类型具体类型的值两部分组成的。这两部分分别成为接口的动态类型动态值

如果我们想要判断接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:

x.(T)
  • x:表示类型为interface{}的变量
  • T:表示断言可能是的类型。

该语法返回两个参数,第一个参数是x转换为T类型后的变量,第二个值是一个布尔值,若为true则断言成功,为false则表示断言失败。

func main(){
    var a interface{}
    a="你好golang"
    v,ok:=a.(string)
    if ok{
        fmt.Prinrln(v)
    }else{
        fmt.Println("断言失败")
    }
}

定义一个方法,可以传入任意数据类型,然后根据不同的类型实现不同的功能

func MyPrint(x interface{}){
    if_,ok:=x.(string);ok{
        fmt.Println("string类型")
    }else if_,ok:=x.(int);ok{
        fmt.Println("int类型")
    }else if_,ok:=x.(bool);ok{
        fmt.Println("bool类型")
    }
}
func main(){
    MyPrintln("你好")
}

x.(type)判断一个变量的类型,这个语句只能用在switch语句里面

func MyPrint2(x interface{}){
    switch x.(type){
        case int:
             fmt.Println("int类型")
        case string:
             fmt.Println("string类型")
        case bool:
             fmt.Println("bool类型")
        default:
             fmt.Println("啥也不是类型")
        
    }
}

上文例子升级的升级

package main
​
import "fmt"type Usber interface {
    start()
    stop()
}
​
//如果结构体里面有方法的话,必须要通过结构体或者通过自定义类型实现这个接口
type phone struct{}
type camera struct{}
type computer struct{}
​
//要实现usb接口的话必须要实现usb接口中的所有方法
func (p phone) start() {
    fmt.Println("手机启动。。。")
}
func (p phone) stop() {
    fmt.Println("手机关机。。。")
}
func (c camera) start() {
    fmt.Println("相机启动。。。")
}
func (c camera) stop() {
    fmt.Println("相机关机。。。")
}
func (cp computer) work(usb Usber) {//升级地方
    //要判断usb的类型
    if_,ok:=usb.(Phone);ok{//类型断言
        usb.start()
    }else{
        usb.stop()
    }   
}
func main() {
    var p = phone{}
    var c = camera{}
    var cp = computer{}
    cp.work(p)
    cp.work(c)
}
​

go中空接口和类型断言使用细节

type Address struct{
    Name string
    Phone int
}
func main(){
    var userinfo = make(map[string]interface{})
    userinfo["age"]=20
    userinfo["hobby"]=[]string{"吃饭","睡觉"}
    fmt.Println(userinfo["age"])//20
    fmt.Println(userinfo["hobby"])//{吃饭 睡觉}
   // fmt.Println(userinfo["hobby"][1])//interface{}类型不支持索引
    var address =Address{
        Name:"李四",
        Phone:12345678910,
    }
    fmt.Println(address.Name)//李四
    userinfo["address"] =address//userinfo map类型的 "address"键对应的值是address接口体
    fmt.Println(userinfo["address"])//{李四 12345678910}
    //var name  = userinfo["address"].name//type interfaceP{}类型 是一个接口类型不支持这种方式去访问元素
    hobby2,_:=userinfo["hobby"].([]string)//断言该键是不是字符串类型切片是的话就让断言后的类型交给hobbby2
    fmt.Println(hobby2[0])//吃饭
    address2,_:=userinfo["address"].(Address)
    fmt.Println(address2.Name,address2.Phone)//李四 12345678910
}

结构体值接收者和指针接收者实现接口的区别

值接收者: 如果结构体中的方法是值接收者,那么实例化后的结构体值类型和结构体指针类型都可以赋值给接口变量

type Usber interface{
    start()
}
type Phone struct{
    name string
}
func (p Phone) start(){
    p.name = "小米"
    fmt.Println(p.name+"手机")
}
​
func main(){
    var p = Phone{"华为"}
    var p1 Usber = p
    p.start()//小米手机
    fmt.Println(p.name+"手机")//华为手机
    var pp = Phone{"华为"}
    var p2 Usber = &pp
    p.start()//小米手机
    fmt.Println(pp.name+"手机")//小米手机
}

指针接收者: 如果结构体中的方法是指针接收者,那么实例化后结构体指针类型都可以赋值给接口变量,结构体值类型没法赋值给接口变量。

type Usber interface{
    start()
}
type Phone struct{
    name string
}
func (p *Phone) start(){
    p.name = "小米"
    fmt.Println(p.name+"手机")
}
​
func main(){
    var p = Phone{"华为"}
    var p1 Usber = p
   // p.start()//报错
    //fmt.Println(p.name+"手机")
    var pp =Phone{"华为"}
    var p2 Usber = &pp
    p.start()//小米手机
    fmt.Println(pp.name+"手机")//小米手机
}

一个结构体实现多个接口(ocp ->Open Close Principle->"开闭原则")

go中一个结构体也可以实现多个接口 (对扩展是开放的,对修改是关闭的)

type Animal1 interface{
    setName(string)
}
type Animal2 interface{
    getName()string
}
type dog struct{
    name string
}
func (d *dog) setName(name string){
    d.name=name
}
func (d *dog) getName()string{
    return d.name
}
func main(){
    var d = &dog{name:"小胖"}
    var a Animal1 = d
    var b Animal2 = d
    a.setName("小黑")
    b.getName()
    fmt.Printf("b.getName(): %v\n", b.getName())
​
}

接口嵌套

接口与接口间之间可以通过嵌套创造出新的接口

type A interface{
    setName(string)
}
type B interface{
    getName() string
}
type Animaler interface{
    A
    B
}
type Dog struct{
    name string
}
func (d *Dog) setName(name string){
    d.name = name
}
func (d Dog) getName() string{
    return d.name
}
func main() {
    var d = &Dog{name: "小胖"}
    var a Animaler = d
    a.setName("小黑")
    fmt.Printf("b.getName(): %v\n", a.getName())
}

14.包

包有区分命名空间(一个文件夹中不能有两个同名文件),也可以更好的管理项目。

go中创建一个包,一般是创建一个文件夹在该文件夹里面的go文件中,使用package关键词声明包名称,

通常,文件夹名称和包名称相同。并且,同一个文件夹下面只有一个包。

创建包

  1. 创建一个dao的文件夹
  2. 创建一个dao.go文件
  3. 在该文件中声明包
package dao
​
import "fmt"func Test1() {
    fmt.Println("test package")
}
​

导入包

要使用某个包下面的变量或者方法,需要导入该包,在service.go中导入dao

package service
import "dao"
func main(){
dao.Test1()
}

包注意事项

  • 一个文件夹下只能由一个package

    • import后面是GOPATH/go mod开始的相对路径,包括最后一段。但由于一个目录下只能有一个package,所以import一个路径就等于import了这个路径下的包。
    • 注意,这里指的是"直接包含"的go文件。如果有子目录,子目录的父目录是完全两个包。
  • 比如你实现了一个计算器package,名叫calc,位于calc目录下;但又想给别人一个使用范例,于是在calc下可以建个example子目录(calc/example/),这个子目录里有个example.go(calc/example/example.go)。此时,example.go包可以是main包,里面还可以有个main函数。

  • 一个package的文件不能在多个文件夹下

    • 如果多个文件夹下有重名的package,它们其实是彼此无关的package。
    • 如果一个go文件需要同时使用不同目录下的同名package,需要在import这些目录时为每个目录指定一个package的别名

go包管理工具go module

go modules 是golang1.11新加的特性,用来管理模块中包的依赖关系。

go mod使用方法

  • 初始化模块

    go mod init <项目名称>

  • 依赖关系处理,根据go.mod文件

    go mod tidy

  • 将依赖包复制到项目下的vendor目录

    go mod vendor

    如果包被屏蔽(墙),可以使用这个命令,随后使用go build -mod=vendor编译

  • 显示依赖关系

    go list -m all

  • 显示详细依赖关系

    go list -m -json all

  • 下载依赖

    go mod download [path@version]

    [path@version]是非必写的

青训营也快结束了,祝大家前程似锦,陌上花开!