Golang 基本语法(3)

466 阅读25分钟

Go中的日期函数

time包

时间和日期是我们编程中经常会用到的,在golang中time包提供了时间的显示和测量用的函数。

time.Now获取当前时间

timeObj := time.Now()
year := timeObj.Year()
month := timeObj.Month()
day := timeObj.Day()
fmt.Printf("%d-%02d-%02d \n", year, month, day)

格式化日期

时间类型有一个自带的方法 Format进行格式化

需要注意的是Go语言中格式化时间模板不是长久的 Y-m-d H:M:S

而是使用Go的诞生时间 2006年1月2日 15点04分 (记忆口诀:2006 1 2 3 4 5)

/**
        时间类型有一个自带的方法 Format进行格式化
        需要注意的是Go语言中格式化时间模板不是长久的 Y-m-d H:M:S
        而是使用Go的诞生时间 2006年1月2日 15点04分 (记忆口诀:2006 1 2 3 4 5)
     */
timeObj2 := time.Now()
// 24小时值  (15表示二十四小时)
fmt.Println(timeObj2.Format("2006-01-02 15:04:05"))
// 12小时制
fmt.Println(timeObj2.Format("2006-01-02 03:04:05"))

获取当前时间戳

时间戳是自1070年1月1日(08:00:00GMT)至当前时间的总毫秒数。它也被称为Unix时间戳

/**
    获取当前时间戳
     */
timeObj3 := time.Now()
// 获取毫秒时间戳
unixTime := timeObj3.Unix()
// 获取纳秒时间戳
unixNaTime := timeObj3.UnixNano()

时间戳转日期字符串

通过将时间戳我们可以转换成日期字符串

// 时间戳转换年月日时分秒(一个参数是秒,另一个参数是毫秒)
var timeObj4 = time.Unix(1595289901, 0)
var timeStr = timeObj4.Format("2006-01-02 15:04:05")
fmt.Println(timeStr)

日期字符串转换成时间戳

// 日期字符串转换成时间戳
var timeStr2 = "2020-07-21 08:10:05";
var tmp = "2006-01-02 15:04:05"
timeObj5, _ := time.ParseInLocation(tmp, timeStr2, time.Local)
fmt.Println(timeObj5.Unix())

时间间隔

time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间,以纳秒为单位。time.Duration表示一段时间间隔,可表示的最大长度段大约290年。

time包中定义的时间间隔类型的常量如下:

image-20200721081402315.png

时间操作函数

我们在日常的编码过程中可能会遇到要求时间+时间间隔的需求,Go语言的时间对象有提供Add方法如下

func (t Time) Add(d Duration)Time

例如

// 时间相加
now := time.Now()
// 当前时间加1个小时后
later := now.Add(time.Hour)
fmt.Println(later)

同理的方法还有:时间差、判断相等

定时器

方式1:使用time.NewTicker(时间间隔)来设置定时器

// 定时器, 定义一个1秒间隔的定时器
ticker := time.NewTicker(time.Second)
n := 0
for i := range ticker.C {
    fmt.Println(i)
    n++
    if n>5 {
        // 终止定时器
        ticker.Stop()
        return
    }
}

方式2:time.Sleep(time.Second)来实现定时器

for  {
    time.Sleep(time.Second)
    fmt.Println("一秒后")
}

Go中的指针

要搞明白Go语言中的指针需要先知道三个概念

  • 指针地址
  • 指针类型
  • 指针取值

Go语言中的指针操作非常简单,我们只需要记住两个符号:&:取地址,*:根据地址取值

关于指针

我们知道变量是用来存储数据的,变量的本质是给存储数据的内存地址起了一个好记的别名。比如我们定义了一个变量a:=10,这个时候可以直接通过a这个变量来读取内存中保存的10这个值。在计算机底层a这个变量其实对应了一个内存地址。

指针也是一个变量,但它是一种特殊的变量,它存储的数据不是一个普通的值,而是另一个变量的内存地址。

image-20200721083711830.png

指针地址和指针类型

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go 语言中使用&字符放在变量前面对变量进行取地址操作。Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:

*int、,*int64、*string

取变量指针的语法如下:

ptr := &v

其中:

  • v:代表被取地址的变量,类型为T
  • ptr:用于接收地址的变量,ptr的类型就为T,被称做T的指针类型。 代表指针

举个例子:

image-20200721084549011.png

image-20200721084549011.png

指针取值

在对普通变量进行&操作符取地址后,会获得这个变量指针,然后可以对指针使用*操作,也就是指针取值

// 指针取值
var c = 20
// 得到c的地址,赋值给d
var d = &c
// 打印d的值,也就是c的地址
fmt.Println(d)
// 取出d指针所对应的值
fmt.Println(*d)
// c对应地址的值,改成30
*d = 30
// c已经变成30了
fmt.Println(c)

改变内存中的值,会直接改变原来的变量值

// 这个类似于值传递
func fn4(x int) {
    x = 10
}
// 这个类似于引用数据类型
func fn5(x *int) {
    *x = 20
}
func main() {
    x := 5
    fn4(x)
    fmt.Println(x)
    fn5(&x)
    fmt.Println(x)
}

我们创建了两个方法,一个是传入局部变量,一个是传入指针类型,最后运行得到的结果

5
20

new和make函数

需要注意的是,指针必须在创建内存后才可以使用,这个和 slice 和 map是一样的

// 引用数据类型map、slice等,必须使用make分配空间,才能够使用
var userInfo = make(map[string]string)
userInfo["userName"] = "zhangsan"
fmt.Println(userInfo)
​
var array = make([]int, 4, 4)
array[0] = 1
fmt.Println(array)

对于指针变量来说

// 指针变量初始化
var a *int
*a = 100
fmt.Println(a)

执行上面的代码会引发panic,为什么呢?在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。要分配内存,就引出来今天的new和make。Go 语言中new和make是内建的两个函数,主要用来分配内存。

这个时候,我们就需要使用new关键字来分配内存,new是一个内置的函数,它的函数签名如下:

func new(Type) *Type

其中

  • Type表示类型,new函数只接受一个参数,这个参数是一个类型
  • *Type表示类型指针,new函数返回一个指向该类型内存地址的指针

实际开发中new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。举个例子:

// 使用new关键字创建指针
aPoint := new(int)
bPoint := new(bool)
fmt.Printf("%T \n", aPoint)
fmt.Printf("%T \n", bPoint)
fmt.Println(*aPoint)
fmt.Println(*bPoint)

本节开始的示例代码中 var a *int 只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应该按照如下方式使用内置的

make和new的区别

  • 两者都是用来做内存分配的
  • make只能用于slice、map以及channel的初始化,返回的还是这三个引用类型的本身
  • 而new用于类型的内存分配,并且内存赌赢的值为类型的零值,返回的是指向类型的指针

Go中的结构体

关于结构体

Golang中没有“类”的概念,Golang中的结构体和其他语言中的类有点相似。和其他面向对象语言中的类相比,Golang中的结构体具有更高的扩展性和灵活性。

Golang中的基础数据类型可以装示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型就无法满足需求了,Golang提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。也就是我们可以通过struct来定义自己的类型了。

Type关键字

Golang中通过type关键词定义一个结构体,需要注意的是,数组和结构体都是值类型,在这个和Java是有区别的

自定义类型

在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型,Go语言中可以使用type关键字来定义自定义类型。

type myInt int

上面代码表示:将mylnt定义为int类型,通过type关键字的定义,mylnt就是一种新的类型,它具有int的特性。

示例:如下所示,我们定义了一个myInt类型

type myInt int
func main() {
    var a myInt = 10
    fmt.Printf("%v %T", a, a)
}

输出查看它的值以及类型,能够发现该类型就是myInt类型

10 main.myInt

除此之外,我们还可以定义一个方法类型

func fun(x int, y int)int {
    return x + y
}
func main() {
    var fn myFn = fun
    fmt.Println(fn(1, 2))
}

然后调用并输出

3

类型别名

Golang1.9版本以后添加的新功能

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有大名、小名、英文名,但这些名字都指的是他本人

type TypeAlias = Type

我们之前见过的rune 和 byte 就是类型别名,他们的底层代码如下

type byte = uint8
type rune = int32

结构体定义和初始化

结构体的定义

使用type 和 struct关键字来定义结构体,具体代码格式如下所示:

/**
    定义一个人结构体
 */
type Person struct {
    name string
    age int
    sex string
}
func main() {
    // 实例化结构体
    var person Person
    person.name = "张三"
    person.age = 20
    person.sex = "男"
    fmt.Printf("%#v", person)
}

注意:结构体首字母可以大写也可以小写,大写表示这个结构体是公有的,在其它的包里面也可以使用,小写表示结构体属于私有的,在其它地方不能使用

例如:

type Person struct {
    Name string
    Age int
    Sex string
}

实例化结构体

刚刚实例化结构体用到了:var person Person

// 实例化结构体
var person Person
person.name = "张三"
person.age = 20
person.sex = "男"

实例化结构体2

我们下面使用另外一个方式来实例化结构体,通过new关键字来实例化结构体,得到的是结构体的地址,格式如下

var person2 = new(Person)
person2.name = "李四"
person2.age = 30
person2.sex = "女"
fmt.Printf("%#v", person2)

输出如下所示,从打印结果可以看出person2是一个结构体指针

&main.Person{name:"李四", age:30, sex:"女"}

需要注意:在Golang中支持对结构体指针直接使用,来访问结构体的成员

person2.name = "李四"
// 等价于
(*person2).name = "李四"

实例化结构体3

使用&对结构体进行取地址操作,相当于对该结构体类型进行了一次new实例化操作

// 第三种方式实例化
var person3 = &Person{}
person3.name = "赵四"
person3.age = 28
person3.sex = "男"
fmt.Printf("%#v", person3)

实例化结构体4

使用键值对的方式来实例化结构体,实例化的时候,可以直接指定对应的值

// 第四种方式初始化
var person4 = Person{
    name: "张三",
    age: 10,
    sex: "女",
}
fmt.Printf("%#v", person4)

实例化结构体5

第五种和第四种差不多,不过是用了取地址,然后返回的也是一个地址

// 第五种方式初始化
var person5 = &Person{
    name: "孙五",
    age: 10,
    sex: "女",
}
fmt.Printf("%#v", person5)

实例化结构体6

第六种方式是可以简写结构体里面的key

var person6 = Person{
    "张三",
    5,
    "女",
}
fmt.Println(person6)

结构体方法和接收者

在go语言中,没有类的概念但是可以给类型(结构体,自定义类型)定义方法。所谓方法就是定义了接收者的函数。接收者的概念就类似于其他语言中的this 或者self。

方法的定义格式如下:

func (接收者变量 接收者类型) 方法名(参数列表)(返回参数) {
    函数体
}

其中

  • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为p,Connector类型的接收者变量应该命名为c等。、

  • 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。

    • 非指针类型:表示不修改结构体的内容
    • 指针类型:表示修改结构体中的内容
  • 方法名、参数列表、返回参数:具体格式与函数定义相同

如果示例所示:

/**
    定义一个人结构体
 */
type Person struct {
    name string
    age int
    sex string
}
​
// 定义一个结构体方法
func (p Person) PrintInfo() {
    fmt.Print(" 姓名: ", p.name)
    fmt.Print(" 年龄: ", p.age)
    fmt.Print(" 性别: ", p.sex)
    fmt.Println()
}
func (p *Person) SetInfo(name string, age int, sex string)  {
    p.name = name
    p.age = age
    p.sex = sex
}
​
func main() {
    var person = Person{
        "张三",
        18,
        "女",
    }
    person.PrintInfo()
    person.SetInfo("李四", 18, "男")
    person.PrintInfo()
}

运行结果为:

 姓名: 张三 年龄: 18 性别: 女
 姓名: 李四 年龄: 18 性别: 男

注意,因为结构体是值类型,所以我们修改的时候,因为是传入的指针

func (p *Person) SetInfo(name string, age int, sex string)  {
    p.name = name
    p.age = age
    p.sex = sex
}

给任意类型添加方法

在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。

举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。

type myInt int
func fun(x int, y int)int {
    return x + y
}
func (m myInt) PrintInfo()  {
    fmt.Println("我是自定义类型里面的自定义方法")
}
func main() {
    var a myInt = 10
    fmt.Printf("%v %T \n", a, a)
    a.PrintInfo()
}

结构体的匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就被称为匿名字段

匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能一个

/**
    定义一个人结构体
 */
type Person struct {
    string
    int
}
​
func main() {
    // 结构体的匿名字段
    var person = Person{
        "张三",
        18
    }
}

结构体的字段类型可以是:基本数据类型,也可以是切片、Map 以及结构体

如果结构体的字段类似是:指针、slice、和 map 的零值都是nil,即还没有分配空间

如果需要使用这样的字段,需要先make,才能使用

/**
    定义一个人结构体
 */
type Person struct {
    name string
    age int
    hobby []string
    mapValue map[string]string
}
​
func main() {
    // 结构体的匿名字段
    var person = Person{}
    person.name = "张三"
    person.age = 10// 给切片申请内存空间
    person.hobby = make([]string, 4, 4)
    person.hobby[0] = "睡觉"
    person.hobby[1] = "吃饭"
    person.hobby[2] = "打豆豆"// 给map申请存储空间
    person.mapValue = make(map[string]string)
    person.mapValue["address"] = "北京"
    person.mapValue["phone"] = "123456789"// 加入#打印完整信息
    fmt.Printf("%#v", person)
}

同时我们还支持结构体的嵌套,如下所示

// 用户结构体
type User struct {
    userName string
    password string
    sex string
    age int
    address Address // User结构体嵌套Address结构体
}
​
// 收货地址结构体
type Address struct {
    name string
    phone string
    city string
}
​
func main() {
    var u User
    u.userName = "moguBlog"
    u.password = "123456"
    u.sex = "男"
    u.age = 18
    
    var address Address
    address.name = "张三"
    address.phone = "110"
    address.city = "北京"
    u.address = address
    fmt.Printf("%#v", u)
}

嵌套结构体的字段名冲突

嵌套结构体内部可能存在相同的字段名,这个时候为了避免歧义,需要指定具体的内嵌结构体的字段。(例如,父结构体中的字段 和 子结构体中的字段相似)

默认会从父结构体中寻找,如果找不到的话,再去子结构体中在找

如果子类的结构体中,同时存在着两个相同的字段,那么这个时候就会报错了,因为程序不知道修改那个字段的为准。

结构体的继承

结构体的继承,其实就类似于结构体的嵌套,如下所示,我们定义了两个结构体,分别是Animal 和 Dog,其中每个结构体都有各自的方法,然后通过Dog结构体 继承于 Animal结构体

// 用户结构体
type Animal struct {
    name string
}
func (a Animal) run() {
    fmt.Printf("%v 在运动 \n", a.name)
}
// 子结构体
type Dog struct {
    age int
    // 通过结构体嵌套,完成继承
    Animal
}
func (dog Dog) wang()  {
    fmt.Printf("%v 在汪汪汪 \n", dog.name)
}
​
func main() {
    var dog = Dog{
        age: 10,
        Animal: Animal{
            name: "阿帕奇",
        },
    }
    dog.run();
    dog.wang();
}

运行后,发现Dog拥有了父类的方法

阿帕奇 在运动 
阿帕奇 在汪汪汪

Go中的结构体和Json相互转换

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。RESTfull Api接口中返回的数据都是json数据。

{
    "name": "张三",
    "age": 15
}

比如我们Golang要给App或者小程序提供Api接口数据,这个时候就需要涉及到结构体和Json之间的相互转换 Golang JSON序列化是指把结构体数据转化成JSON格式的字符串,Golang JSON的反序列化是指把JSON数据转化成Golang中的结构体对象

Golang中的序列化和反序列化主要通过“encoding/json”包中的 json.Marshal() 和 son.Unmarshal()

// 定义一个学生结构体,注意结构体的首字母必须大写,代表公有,否则将无法转换
type Student struct {
    ID string
    Gender string
    Name string
    Sno string
}
func main() {
    var s1 = Student{
        ID: "12",
        Gender: "男",
        Name: "李四",
        Sno: "s001",
    }
    // 结构体转换成Json(返回的是byte类型的切片)
    jsonByte, _ := json.Marshal(s1)
    jsonStr := string(jsonByte)
    fmt.Printf(jsonStr)
}

将字符串转换成结构体类型

// 定义一个学生结构体,注意结构体的首字母必须大写,代表公有,否则将无法转换
type Student struct {
    ID string
    Gender string
    Name string
    Sno string
}
func main() {
    // Json字符串转换成结构体
    var str = `{"ID":"12","Gender":"男","Name":"李四","Sno":"s001"}`
    var s2 = Student{}
    // 第一个是需要传入byte类型的数据,第二参数需要传入转换的地址
    err := json.Unmarshal([]byte(str), &s2)
    if err != nil {
        fmt.Printf("转换失败 \n")
    } else {
        fmt.Printf("%#v \n", s2)
    }
}
​

注意

我们想要实现结构体转换成字符串,必须保证结构体中的字段是公有的,也就是首字母必须是大写的,这样才能够实现结构体 到 Json字符串的转换。

结构体标签Tag

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

key1:"value1" key2:"value2"

结构体tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。

注意事项:为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

如下所示,我们通过tag标签,来转换字符串的key

// 定义一个Student体,使用结构体标签
type Student2 struct {
    Id string `json:"id"` // 通过指定tag实现json序列化该字段的key
    Gender string `json:"gender"`
    Name string `json:"name"`
    Sno string `json:"sno"`
}
func main() {
    var s1 = Student2{
        Id: "12",
        Gender: "男",
        Name: "李四",
        Sno: "s001",
    }
    // 结构体转换成Json
    jsonByte, _ := json.Marshal(s1)
    jsonStr := string(jsonByte)
    fmt.Println(jsonStr)
​
    // Json字符串转换成结构体
    var str = `{"Id":"12","Gender":"男","Name":"李四","Sno":"s001"}`
    var s2 = Student2{}
    // 第一个是需要传入byte类型的数据,第二参数需要传入转换的地址
    err := json.Unmarshal([]byte(str), &s2)
    if err != nil {
        fmt.Printf("转换失败 \n")
    } else {
        fmt.Printf("%#v \n", s2)
    }
}

嵌套结构体和Json序列化反序列化

和刚刚类似,我们同样也是使用的是 json.Marshal()

// 嵌套结构体 到 Json的互相转换// 定义一个Student结构体
type Student3 struct {
    Id int
    Gender string
    Name string
}
​
// 定义一个班级结构体
type Class struct {
    Title string
    Students []Student3
}
​
func main() {
    var class = Class{
        Title: "1班",
        Students: make([]Student3, 0),
    }
    for i := 0; i < 10; i++ {
        s := Student3{
            Id: i + 1,
            Gender: "男",
            Name: fmt.Sprintf("stu_%v", i + 1),
        }
        class.Students = append(class.Students, s)
    }
    fmt.Printf("%#v \n", class)
​
    // 转换成Json字符串
    strByte, err := json.Marshal(class)
    if err != nil {
        fmt.Println("打印失败")
    } else {
        fmt.Println(string(strByte))
    }
}

Go中的接口

接口的介绍

现实生活中手机、相机、U盘都可以和电脑的USB接口建立连接。我们不需要关注usb卡槽大小是否一样,因为所有的USB接口都是按照统一的标准来设计的。

image-20200722201435128.png

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

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

Go接口的定义

在Golang中接口(interface)是一种类型,一种抽象的类型。接口(interface)是一组函数method的集合,Golang中的接口不能包含任何变量。

在Golang中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序设计的多态和高内聚低耦合的思想N Golang中的接口也是一种数据类型,不需要显示实现。只需要一个变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。

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

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

其中

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

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

首先我们定义一个Usber接口,接口里面就定义了两个方法

// 定义一个Usber接口
type Usber interface {
    start()
    stop()
}

然后我们在创建一个手机结构体

// 如果接口里面有方法的话,必须要通过结构体或自定义类型实现这个接口// 使用结构体来实现 接口
type Phone struct {
    Name string
}
// 手机要实现Usber接口的话,必须实现usb接口的所有方法
func (p Phone) Start()  {
    fmt.Println(p.Name, "启动")
}
func (p Phone) Stop()  {
    fmt.Println(p.Name, "关闭")
}

然后我们在创建一个Phone的结构体,来实现这个接口

// 如果接口里面有方法的话,必须要通过结构体或自定义类型实现这个接口// 使用结构体来实现 接口
type Phone struct {
    Name string
}
// 手机要实现Usber接口的话,必须实现usb接口的所有方法
func (p Phone) start()  {
    fmt.Println(p.Name, "启动")
}
func (p Phone) stop()  {
    fmt.Println(p.Name, "关闭")
}
func main() {
    var phone Usber = Phone{
        "三星手机",
    }
    phone.start()
    phone.stop()
}

我们在创建一个Camera结构体

// 使用相机结构体来实现 接口
type Camera struct {
    Name string
}
// 相机要实现Usber接口的话,必须实现usb接口的所有方法
func (p Camera) start()  {
    fmt.Println(p.Name, "启动")
}
func (p Camera) stop()  {
    fmt.Println(p.Name, "关闭")
}
func main() {
    var camera Usber = Camera{
        "佳能",
    }
    camera.start()
    camera.stop()
}

我们创建一个电脑的结构体,电脑的结构体就是用于接收两个实现了Usber的结构体,然后让其工作

// 电脑
type Computer struct {
​
}
​
// 接收一个实现了Usber接口的 结构体
func (computer Computer) Startup(usb Usber)  {
    usb.start()
}
​
// 关闭
func (computer Computer) Shutdown (usb Usber)  {
    usb.stop()
}

最后我们在main中调用方法

func main() {
    var camera interfaceDemo.Camera = interfaceDemo.Camera{
        "佳能",
    }
    var phone interfaceDemo.Phone = interfaceDemo.Phone{
        "苹果",
    }
​
    var computer interfaceDemo.Computer = interfaceDemo.Computer{}
    computer.Startup(camera)
    computer.Startup(phone)
    computer.Shutdown(camera)
    computer.Shutdown(phone)
}

运行结果如下所示:

佳能 启动
苹果 启动
佳能 关闭
苹果 关闭

空接口(Object类型)

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

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

// 空接口表示没有任何约束,任意的类型都可以实现空接口
type EmptyA interface {
​
}
​
func main() {
    var a EmptyA
    var str = "你好golang"
    // 让字符串实现A接口
    a = str
    fmt.Println(a)
}

同时golang中空接口也可以直接当做类型来使用,可以表示任意类型。相当于Java中的Object类型

var a interface{}
a = 20
a = "hello"
a = true

空接口可以作为函数的参数,使用空接口可以接收任意类型的函数参数

// 空接口作为函数参数
func show(a interface{}) {
    fmt.println(a)
}

map的值实现空接口

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

// 定义一个值为空接口类型
var studentInfo = make(map[string]interface{})
studentInfo["userName"] = "张三"
studentInfo["age"] = 15
studentInfo["isWork"] = true

slice切片实现空接口

// 定义一个空接口类型的切片
var slice = make([]interface{}, 4, 4)
slice[0] = "张三"
slice[1] = 1
slice[2] = true

类型断言

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

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

x.(T)

其中:

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

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

// 类型断言
var a interface{}
a = "132"
value, isString := a.(string)
if isString {
    fmt.Println("是String类型, 值为:", value)
} else {
    fmt.Println("断言失败")
}

或者我们可以定义一个能传入任意类型的方法

// 定义一个方法,可以传入任意数据类型,然后根据不同类型实现不同的功能
func Print(x interface{})  {
    if _,ok := x.(string); ok {
        fmt.Println("传入参数是string类型")
    } else if _, ok := x.(int); ok {
        fmt.Println("传入参数是int类型")
    } else {
        fmt.Println("传入其它类型")
    }
}

上面的示例代码中,如果要断言多次,那么就需要写很多if,这个时候我们可以使用switch语句来实现:

注意: 类型.(type) 只能结合switch语句使用

func Print2(x interface{})  {
    switch x.(type) {
    case int:
        fmt.Println("int类型")
    case string:
        fmt.Println("string类型")
    case bool:
        fmt.Println("bool类型")
    default:
        fmt.Println("其它类型")
    }
}

结构体接收者

值接收者

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

结构体实现多个接口

实现多个接口的话,可能就同时用两个接口进行结构体的接受

// 定义一个Animal的接口,Animal中定义了两个方法,分别是setName 和 getName,分别让DOg结构体和Cat结构体实现
type Animal interface {
    SetName(string)
}
​
// 接口2
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 dog = &Dog{
        "小黑",
    }
    // 同时实现两个接口
    var d1 Animal = dog
    var d2 Animal2 = dog
    d1.SetName("小鸡")
    fmt.Println(d2.GetName())
}

接口嵌套

在golang中,允许接口嵌套接口,我们首先创建一个 Animal1 和 Animal2 接口,然后使用Animal接受刚刚的两个接口,实现接口的嵌套。

// 定义一个Animal的接口,Animal中定义了两个方法,分别是setName 和 getName,分别让DOg结构体和Cat结构体实现
type Animal1 interface {
    SetName(string)
}
​
// 接口2
type Animal2 interface {
    GetName()string
}
​
type Animal interface {
    Animal1
    Animal2
}
​
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 dog = &Dog{
        "小黑",
    }
    // 同时实现两个接口
    var d Animal = dog
    d.SetName("小鸡")
    fmt.Println(d.GetName())
}

Golang中空接口和类型断言

// golang中空接口和类型断言
var userInfo = make(map[string]interface{})
userInfo["userName"] = "zhangsan"
userInfo["age"] = 10
userInfo["hobby"] = []string{"吃饭", "睡觉"}
fmt.Println(userInfo["userName"])
fmt.Println(userInfo["age"])
fmt.Println(userInfo["hobby"])
// 但是我们空接口如何获取数组中的值?发现 userInfo["hobby"][0]  这样做不行
// fmt.Println(userInfo["hobby"][0])

也就是我们的空接口,无法直接通过索引获取数组中的内容,因此这个时候就需要使用类型断言了

// 这个时候我们就可以使用类型断言了
hobbyValue,ok := userInfo["hobby"].([]string)
if ok {
    fmt.Println(hobbyValue[0])
}

通过类型断言返回来的值,我们就能够直接通过角标获取了。

\