Go 学习笔记 11 | Golang 接口详解

一、Golang 接口

Golang 中接口定义了对象的行为规范,只定义规范不实现。接口中定义的规范由具体的对象来实现。

package main

import (
    "fmt"
)

//接口是一个规范
type Usber interface {  // 最好以 er 结尾表示接口
    start()
    stop()
}

// 如果接口里有方法的话,必须要通过结构体或者通过自定义类型实现这个接口。

type Phone struct {
    Name string
}

// 手机要实现 usb 接口的话必须得实现 usb 接口中的所有方法

func (p Phone) start() {
    fmt.Println(p.Name, "启动")
}

func (p Phone) stop() {
    fmt.Println(p.Name, "关机")
}

func main() {
    p := Phone{
        Name: "华为手机",
    }
    p.start()

    var p1 Usber  // Golang 中接口就是一个数据类型
    p1 = p  // 表示手机实现 Usb 接口

    p1.start()
    p1.stop()
}

输出:

华为手机 启动
华为手机 启动
华为手机 关机

空接口

空接口表示没有任何约束,因此任何类型变量都可以实现空接口。

package main

import (
    "fmt"
)

// Golang 中空接口也可以直接当作类型来使用,可以表示任意类型
func main() {
    var a interface{}  // 空接口可以接收任意类型
    a = 20
    fmt.Printf("值: %v 类型:%T\n", a, a)
    a = "你好golang"
    fmt.Printf("值: %v 类型:%T\n", a, a)
    a = true
    fmt.Printf("值: %v 类型:%T\n", a, a)    
}

输出:

值: 20 类型:int
值: 你好golang 类型:string
值: true 类型:bool

1. 空接口可以作为函数的参数

package main

import (
    "fmt"
)

// 1、空接口可以作为函数的参数
func show(a interface{}) {
    fmt.Printf("值:%v 类型:%T\n", a, a)
}

// Golang 中空接口也可以直接当作类型来使用,可以表示任意类型
func main() {    
    show(20)
    show("你好golang")
    slice := []int{1, 2, 3, 4}
    show(slice)
}

输出:

值:20 类型:int
值:你好golang 类型:string
值:[1 2 3 4] 类型:[]int

2. map 的值实现空接口

package main

import (
    "fmt"
)

// 2、map 的值实现空接口
func show(a interface{}) {
    fmt.Printf("值:%v 类型:%T\n", a, a)
}

func main() {
    var m1 = make(map[string]interface{})

    m1["username"] = "张三"
    m1["age"] = 20
    m1["married"] = true

    fmt.Println(m1)

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

输出:

map[age:20 married:true username:张三]
[1 2 你好 true]

类型断言

package main

import (
    "fmt"
)

func main() {
    var a interface{}
    a = "你好golang"
    v, ok := a.(string)
    if ok {
        fmt.Println("a就是一个string类型,值是:", v)
    } else {
        fmt.Println("断言失败")
    }
}

输出:

a就是一个string类型,值是: 你好golang

另一种写法:

package main

import (
    "fmt"
)

// 1、X.(T) 括号里表示 X 可能是的类型
func MyPrint1(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类型")
    }
}

// 2、类型.(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("传入错误...")
    }
}

func main() {
    MyPrint1("你好golang")
    MyPrint1(true)
    MyPrint1(20)


    MyPrint2(true)
    MyPrint2(20)
    MyPrint2("你好golang")
}

输出:

string类型
bool类型
int类型
bool类型
int类型
string类型

二、结构体值接收者实现接口

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

package main

import (
    "fmt"
)

// 接口是一个规范
type Usber interface {  // 最好以 er 结尾表示接口
    start()
    stop()
}

// 如果接口里有方法的话,必须要通过结构体或者通过自定义类型实现这个接口。

type Phone struct {
    Name string
}

// 手机要实现 usb 接口的话必须得实现 usb 接口中的所有方法

func (p Phone) start() {
    fmt.Println(p.Name, "启动")
}

func (p Phone) stop() {
    fmt.Println(p.Name, "关机")
}

func main() {
    // 结构体值接收者实例化后的结构体值类型和结构体指针类型都可以赋值给接口变量
    var p1 = Phone{
        Name: "小米手机",
    }

    var p2 Usber = p1  // 表示让 Phone 实现 Usb 的接口
    p2.start()

    var p3 = &Phone{
        Name: "苹果手机",
    }

    var p4 Usber = p3
    p4.start()
}

输出:

小米手机 启动
苹果手机 启动

指针类型

package main

import (
    "fmt"
)

// 接口是一个规范
type Usber interface {  // 最好以 er 结尾表示接口
    start()
    stop()
}

// 如果接口里有方法的话,必须要通过结构体或者通过自定义类型实现这个接口。

type Phone struct {
    Name string
}

// 手机要实现 usb 接口的话必须得实现 usb 接口中的所有方法

func (p *Phone) start() {  // 指针接收者
    fmt.Println(p.Name, "启动")
}

func (p *Phone) stop() {
    fmt.Println(p.Name, "关机")
}


func main() {
    /*
        // 错误写法
        var phone1 = Phone{
            Name: "小米手机",
        }

        var p1 Usber = phone1  // 表示让 Phone 实现 Usb 的接口
        p1.start()
    */

    var phone1 = &Phone{
        Name: "小米",
    }
    var p1 Usber = phone1
    p1.start()
}

输出:

小米 启动

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

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

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

package main

import (
    "fmt"
)

type Animaler interface {
    SetName(string)
    GetName() string
}

type Dog struct {
    Name string
}

func (d *Dog) SetName(name string) {
    d.Name = name
}

func (d Dog) GetName() string {
    return d.Name
}

type Cat struct {
    Name string
}

func (c *Cat) SetName(name string) {
    c.Name = name
}

func (c Cat) GetName() string {
    return c.Name
}

func main() {
    // Dog 实现 Animal 的接口
    d := &Dog{
        Name: "小黑",
    }
    var d1 Animaler = d
    fmt.Println(d1.GetName())
    d1.SetName("小黄")
    fmt.Println(d1.GetName())

    // Cat 实现 Animal 的接口
    c := &Cat{
        Name: "小花",
    }
    var c1 Animaler = c
    fmt.Println(c1.GetName())
}

输出:

小黑
小黄
小花

接口嵌套

package main

import (
    "fmt"
)

type Ainterface interface {
    SetName(string)
}

type Binterface interface {
    GetName() string
}

type Animaler interface {  // 接口的嵌套
    Ainterface
    Binterface
}

type Dog struct {
    Name string
}

func (d *Dog) SetName(name string) {
    d.Name = name
}

func (d Dog) GetName() string {
    return d.Name
}

func main() {
    d := &Dog{
        Name: "小黑",
    }
    var d1 Animaler = d
    d1.SetName("小花")
    fmt.Println(d1.GetName())
}

输出:

小花

三、Golang 中空接口和类型断言使用细节

package main

import (
    "fmt"
)

type Address struct {
    Name string
    Phone int
}

// Golang中空接口和类型断言使用细节
func main() {
    var userinfo = make(map[string]interface{})
    userinfo["username"] = "张三"
    userinfo["age"] = 20
    userinfo["hobby"] = []string{"睡觉", "吃饭"}

    fmt.Println(userinfo["age"])  // 20
    fmt.Println(userinfo["hobby"])  // [睡觉 吃饭]

    var address = Address {
        Name: "李四",
        Phone: 123456,
    }
    fmt.Println(address.Name)  // 李四

    userinfo["address"] = address
    fmt.Println(userinfo["address"])  // {李四 123456}

    hobby2, _ := userinfo["hobby"].([]string)  // 类型断言

    fmt.Println(hobby2[1])  // 吃饭

    address2, _ := userinfo["address"].(Address)  // 类型断言
    fmt.Println(address2.Name, address2.Phone)  // 李四 123456
}

输出:

20
[睡觉 吃饭]
李四
{李四 123456}
吃饭
李四 123456

四、参考教程

Golang 教程 P37-P40

Go学习笔记 文章被收录于专栏

自己学习 Go 语言记录的笔记。

全部评论

相关推荐

头像
10-13 18:10
已编辑
东南大学 C++
。收拾收拾心情下一家吧————————————————10.12更新上面不知道怎么的,每次在手机上编辑都会只有最后一行才会显示。原本不想写凉经的,太伤感情了,但过了一天想了想,凉经的拿起来好好整理,就像象棋一样,你进步最快的时候不是你赢棋的时候,而是在输棋的时候。那废话不多说,就做个复盘吧。一面:1,经典自我介绍2,项目盘问,没啥好说的,感觉问的不是很多3,八股问的比较奇怪,他会深挖性地问一些,比如,我知道MMU,那你知不知道QMMU(记得是这个,总之就是MMU前面加一个字母)4,知不知道slab内存分配器->这个我清楚5,知不知道排序算法,排序算法一般怎么用6,写一道力扣的,最长回文子串反问:1,工作内容2,工作强度3,关于友商的问题->后面这个问题问HR去了,和中兴有关,数通这个行业和友商相关的不要提,这个行业和别的行业不同,别的行业干同一行的都是竞争关系,数通这个行业的不同企业的关系比较微妙。特别细节的问题我确实不知道,但一面没挂我。接下来是我被挂的二面,先说说我挂在哪里,技术性问题我应该没啥问题,主要是一些解决问题思路上的回答,一方面是这方面我准备的不多,另一方面是这个面试写的是“专业面试二面”,但是感觉问的问题都是一些主管面/综合面才会问的问题,就是不问技术问方法论。我以前形成的思维定式就是专业面会就是会,不会就直说不会,但事实上如果问到方法论性质的问题的话得扯一下皮,不能按照上面这个模式。刚到位置上就看到面试官叹了一口气,有一些不详的预感。我是下午1点45左右面的。1,经典自我介绍2,你是怎么完成这个项目的,分成几个步骤。我大致说了一下。你有没有觉得你的步骤里面缺了一些什么,(这里已经在引导我往他想的那个方向走了),比如你一个人的能力永远是不够的,,,我们平时会有一些组内的会议来沟通我们的所思所想。。。。3,你在项目中遇到的最困难的地方在什么方面4,说一下你知道的TCP/IP协议网络模型中的网络层有关的协议......5,接着4问,你觉得现在的socket有什么样的缺点,有什么样的优化方向?6,中间手撕了一道很简单的快慢指针的问题。大概是在链表的倒数第N个位置插入一个节点。————————————————————————————————————10.13晚更新补充一下一面说的一些奇怪的概念:1,提到了RPC2,提到了fu(第四声)拷贝,我当时说我只知道零拷贝,知道mmap,然后他说mmap是其中的一种方式,然后他问我知不知道DPDK,我说不知道,他说这个是一个高性能的拷贝方式3,MMU这个前面加了一个什么字母我这里没记,别问我了4,后面还提到了LTU,VFIO,孩子真的不会。
走呀走:华子二面可能会有场景题的,是有些开放性的问题了
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
11-21 11:29
已编辑
斯卡蒂味的鱼汤:知道你不会来数马,就不捞你😂最近数马疯狂扩招,招聘要求挺低的,你能力肯定够,应该就是因为太强了,知道你不会来才不捞你
投递腾讯云智研发等公司7个岗位
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务