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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容