GO专栏 (四) ——— Go语言的类型系统 - Go语言中文社区

GO专栏 (四) ——— Go语言的类型系统


Go-In-Action

@author 鲁伟林
记录《Go语言实战》中各章节学习过程,写下一些自己的思考和总结。希望维护管理此仓库,记录学习过程中部分心得,以与其他同行参考。

本博客中涉及的完整代码:
GitHub地址: https://github.com/thinkingfioa/go-learning
本人博客地址: https://blog.csdn.net/thinking_fioa
文中如若有任何错误,欢迎指出。个人邮箱: thinking_fioa@163.com

第5章 Go语言的类型系统

Go语言是静态编译的语言。Go语言提供基本类型,同时也允许用户自定义类型

5.1 用户定义类型

Go语言里声明用户定义的类型有两种方法,第一种:使用关键字struct(最常用),第二种:基于一个已有的类型,将其作为新类型

5.1.1 使用关键字struct定义类型

使用关键字struct定义类型。如下代码:

type user struct {
    name  string
    email string
}

5.1.1.1 使用关键字var声明

使用var声明变量时,这个变量对应的值总是会被初始化。如: var wllu user。对于数值类型来说,零值是0; 对于字符串来说,零值是空串;对于布尔类型,零值是false

5.1.1.2 结构字面量

使用结构字面量和短变量声明操作符来创建变量。其中(:=)称之为短变量声明操作符。

声明每个字段的名字以及对应的值

声明每个字段的名字以及对应的值

u := user {
    name: "thinking",
    email: "thinking_fioa@163.com",
}

没有字段名,只声明对应的值

// 没有字段名,只声明对应的值
u := user{"thinking", "thinking_fioa@163.com"}

5.1.2 基于已有的类型,将其作为新类型的类型

使用关键字type,类型于重命名,但编译器不会对不同的类型的值做隐式转换。该方式提高了可读性。eg: type Duration int64

5.2 方法

方法能给用户定义的类型添加新的行为。方法实际上也是函数,只是在声明时,在关键字func和方法名之间添加一个参数。

关键字func和函数名之间的参数被称为接收者。Go语言里有两种类型的接收者:值接收者和指针接收者

Go语言既允许使用值,也允许使用指针来调用方法,不必严格符合接收者的类型。Go语言编译器会自动帮助转换,以符合方法声明的接收者。

5.2.1 值接收者

func (u user) notify() {}

值接收者调用时会使用这个值的一个副本来执行,意味着你对这个值的副本做了任何修改,原来调用者不受影响

5.2.2 指针接受者

func (u *user) notify() {}

指针接收者使用实际的值来执行,意味着方法中对值的做了任何修改,原来的值将同步变化

总结

值接收者使用值的副本来调用方法,而指针接收者使用实际值来调用方法

5.3 类型的本质

为一个类型添加方法时,需要考虑该类型的方法是值接收者还是指针接受者。如果是要创建一个新值,就使用值接收者。如果是要修改当前值,就使用指针接收者。

5.3.1 内置类型

数值类型、字符串类型和布尔类型都是内置类型。内置类型都是以副本在方法或函数间传递,对这些值进行增加或者删除的时候,会创建一个新值。

5.3.2 引用类型

Go语言中的引用类型有如下几个:切片、映射、通道、接口和函数类型。通过复制来传递一个引用类型的值的副本,本质上是在共享底层数据结构。

5.3.3 结构类型

原始本质的类型和非原始本质类型。原始本质类型指的是应该被复制,而不应该被共享的类型。非原始本质类型指的是应该被共享,不应该被复制。

是使用值接收者还是指针接收者,不应该由该方法是否修改了接收到的值来决定。这个决策应该基于该类型的本质。这个规则由一个例外,需要让类型值符合某个接口时,即便类型的本质是非原始的,也可以使用值接收者声明方法。

5.4 接口

接口的出现是为了实现多态。多态是指代码可以根据类型的值的具体实现采取不同行为的能力。

5.4.2 实现

接口是用来定义行为的类型。如果用户定义的类型实现了某个接口类型声明的一组方法,那么这个用户定义的类型的值就可以赋给这个接口类型的值。用于定义的类型的值通常称为实体类型。

接口值分为两种,第一个:实体值赋值给接口值,第二个:实体指针赋值给接口值。接口值是一个两个字长度的数据结构,第一个字包含一个指向内部表(iTable)的指针,包含了所存储的值的类型信息。第二个字是一个指向所存储值的指针

 

5.4.3 方法集

方法集定义了一组关联到给定类型的值或者指针的方法。定义方法时使用的接收者的类型决定了这个方法关联到值,还是关联到指针,还是两个都关联。请与5.2章节区分,这里Go语言编译器不会自动帮助转换。

从接收者的角度看这些规则: 

如果使用值接收者来实现一个接口,那么那个类型的值和指针都能赋值给对应的接口。如果使用指针接收者实现一个接口,那么只能指向那个类型的指针才能赋值给对应的接口。如有不懂,可参考listing36.go和listing36P.go帮助理解

5.5 嵌入类型

Go语言允许用户扩展或修改已有类型的行为,这对于代码复用很有好处。这个功能是通过嵌入类型实现的。嵌入类型是将已有的类型直接声明在新的结构类型里

通过嵌入类型,与内部类型相关的标识符会提升到外部类型上。这些被提升的标识符就像直接声明在外部类型里的标识符一样,也是外部类型的一部分。这样外部类型就组合了内部类型包含的所有属性,并且可以添加新的字段和方法。外部类型可通过声明与内部类型标识符同名的标识符来覆盖内部标识符的字段或方法。

type notifier interface {
    notify()
}

type user struct {
    name  string
    email string
}

func (u user) notify() {
    fmt.Printf("name %s, email %sn", u.name, u.email)
}

type admin struct {
    user
    password string
}

func main() {

    ad := admin{
        user: user{
            name:  "thinking_fioa",
            email: "thinking_fioa@163.com",
        },
        password: "123456",
    }
    // 可以直接访问内部类型的方法
    ad.user.notify()
    // 内部类型的方法也被提升到外部类型中
    ad.notify()
}

注:

  1. 要嵌入一个类型,只需要声明这个类型的名字就可以了
  2. 通过嵌入类型,与内部类型相关的标识符(包括属性和方法)会提升到外部类型上
  3. 通过内部类型的名字可以访问内部类型的值
  4. 由于内部类型相关的标识符被提升到外部类型上,也可以直接通过外部类型访问
  5. 内部类型的提升,内部类型实现的接口会自动提升到外部类型。外部类型(admin)也实现了接口(notifier)

外部类型可通过声明与内部类型标识符同名的标识符来覆盖内部标识符的字段或方法。亦可实现对应的接口

type notifier interface {
    notify()
}

type user struct {
    name  string
    email string
}

func (u user) notify() {
    fmt.Printf("name is %s, email is %sn", u.name, u.email)
}

type admin struct {
    user
    password string
}

func (ad admin) notify() {
    fmt.Printf("name is %s, email is %s, passwd is %sn", ad.name, ad.email, ad.password)
}

func main() {
    ad := admin{
        user: user{
            name:  "thinking",
            email: "thinking_fioa@163.com",
        },
        password: "123456",
    }

    sendNotification(ad.user)
    sendNotification(ad)
}

func sendNotification(n notifier) {
    n.notify()
}

注:

  1. 由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。这就意味着由于内部类型的实现,外部类型也同样实现了这个接口
  2. 如果外部类型覆盖内部标识符的方法,则内部类型的实现将不会被提升。不过内部类型的值一直存在,可通过内部类型的值来调用内部实现的方法。

5.6 公开或未公开的标识符

Go语言支持从包里公开或者隐藏标识符。当一个标识符的名字以小写字母开头时,表示这个标识符未公开,即包外的代码不可见。如果一个标识符以大写字母开头,表示该标识符是公开的,包外的代码可见。通常将代码所在的文件夹名作为包名

package counters
type alertCounter int


func New(value int) alertCounter {
    return alertCounter(value)
}
import (
    "./counters"
    "fmt"
)

func main() {
    counter := counters.New(1)
    fmt.Printf("count %d", counter)
}

注:

  1. 将工厂函数命名为New是Go语言的一个习惯
  2. New函数返回了一个未公开的alertCount类型的值。原因有两个,第一,标识符才有公开或者未公开,值永远没有。第二,短变量声明操作符,有能力捕获引用的类型,并创建一个未公开的的类型变量。
package entities

type user struct {
    Name  string
    Email string
}

type Admin struct {
    user
    Passwd string
}
import (
    "./entities"
    "fmt"
)

func main() {

    ad := entities.Admin{
        Passwd: "123456",
    }
    ad.Name = "thinking"
    ad.Email = "thinking_fiao@163.com"

    fmt.Printf("name %s, email %s, passwd %s", ad.Name, ad.Email, ad.Passwd)
}

注:

  1. 内部类型user未公开,无法直接通过结构字面量的方式初始化内部类型
  2. 内部类型的标识符全部被提升至外部类型,所以这些公开的字段(Name、Email)可通过外部类型的值直接来访问
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/thinking_fioa/article/details/89289876
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-06-13 15:50:59
  • 阅读 ( 706 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢