《Go in Action》第五章 类型系统

首先,go是一门静态类型语言。即编译器知道每一个值的类型。

如何定义一个类型?

A: 使用关键字type和struct:

type user struct {
  name string
  email string
  ext int
  privileged bool
}

注意每一行没有逗号。
创建一个类型的值:

var rain user
lisa := user {
  nae: "Lisa",
  email: "lisa@email.com",
  ext: 123,
  privileged: true,
}

lisa1 := user{"Lisa", "lisa@email.com", 123, true} //最后一个逗号没有

注意每一行都有逗号,最后一行也有。

struct的属性可以是struct类型:

type admin struct {
  u user
  level string
}

fred := admin {
  u: user {
    name: "Fred",
    email: "fred@email.com",
    ext: 123,
    privileged: true,
  },
  level: "super",
}

另一种创建类型的方式:

type Duration int64

注意此时,编译器认为Duration和int64不是同一种类型。此时,我们称int64是Duration的"base type"

方法

方法是一种给类型增加行为的方式。

type user struct {
  name string
  email string
}

func (u user) notify() {
  fmt.Printf("Sending User Email to %s<%s>\n", u.name, u.email)
}

func (u *user) changeEmail(email string) {
  u.email = email
}

func和方法名之间的参数称为“receiver"。当一个函数具有receiver的时候,我们就称该函数为方法。

receiver具有两种类型:

  • value receiver。即值传递。
  • pointer receiver。传递的是类型的指针

两个不同类型的方法都可以被类型的值或者指针调用。如:

bill := user{"bill", "bill@email.com"}
bill.notify()

lisa := &user{"lisa", "lisa@email.com"}
lisa.changeEmail("lisayou")
lisa.notify()

go会自动进行(*lisa).notify()(&bill).changeEmail()的转换。

一般来说,当使用value receiver的时候表明不需要改变值,使用pointer receiver的时候需要改变值。

接口

A: 使用type和interface定义接口:

type notifier interface{
  notify()
}

注意这里没有定义接口的返回值和参数,因为返回值和参数都没有。

Q: 如何使用接口?

A: 见代码:

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

Q: 如何实现接口?

A: 类型的方法具有同接口一样的名字、参数和返回值,就认为该类型实现了接口。无需显示声明。
比如:

type user struct {
  name string
  email string
}

func (u user) notify(){

}

此时user实现了notifier接口。

Q: point receiver的方法算接口实现吗?

A: 看一个例子:

type user struct {
  name string
  email string
}
func (u *user) notify(){
}

此时user算是实现了notifier了吗?
先来了解一下methods sets:

Method sets define the set of methods that are associated with values or pointers of a given type. The type of receiver used will determine whether a method is associated with a value, pointer, or both.

method sets定义了一个值或者指针类型的方法集。方法的receiver的类型决定了一个方法是同值、指针或者两者都进行了关联。听起来一头雾水。

image.png

这个表的意思是:

  • 类型T的值的method sets仅包含value receiver的方法
  • 类型*T的值的method sets既包含value receiver的方法也包含pointer receiver的方法

从method receiver的角度来看:


image.png

这个的意思就是:

  • 如果使用pointer receiver实现了某个接口,那么实际上是该类型的指针实现了该接口
  • 如果使用value receiver实现了某个接口,那么该类型的值和指针类型都实现了该接口

上面的代码的方法是pointer receiver,所以仅有user类型的指针类型实现了接口。

为什么这么定义,是因为并不是总能获取某个值的地址:

type duration int

func (d *duration) pretty() string {
  return fmt.Sprintf("Duration: %d", *d)
}

func main() {
  duration(42).pretty()
}

Q: 什么是多态?

A: 简单来说:多态就是,声明的时候是接口,传入的时候是该接口的实现。不同的实现具有不同的行为。

Q: type embedding是什么?

A: 先看原文:

This is accomplished through type embedding. It works by taking an existing type and declaring that type within the declaration of a new struct type. The type that is embedded is then called an inner type of the new outer type.

简单来说,就是将一个已经存在的类型(type)放在一个新类型中声明。
被嵌入的类型称为inner type,新类型称为outer type。

有什么作用呢?

Through inner type promotion, identifiers from the inner type are promoted up to the outer type. These promoted identifiers become part of the outer type as if they were declared explicitly by the type itself.

简单来说,inner type的属性和方法就像是outer type自己定义的一样。同时outer type也可以覆盖inner type的属性和方法。看一个例子:

package main

import (
  "fmt"
)

type user struct {
  name string
  emaill string
}

func (u *user) notify() {
  fmt.Printf("Sending user email to %s<%s>\n", u.name, u.email)
}

type admin struct {
  user  // Embedded Type
  level string
}

func main() {
  ad := admin{
    user: user{
      name: "john smith",
      email: "john@yahoo.com",
    },
    level: "super",
  }
  ad.user.notify()
  ad.notify()
}

我们也可以覆盖user里面的方法,比如:

func (a *admin) notify() {
  fmt.Printf("Sending admin email to %s<%s>\n", a.name, a.email)
}

此时,由于admin内嵌了user,user实现了notifier接口,因此admin其实也实现了notifier接口。

导出和不导出identifiers

简单来说,就是包里面的小写字母开头的标识符是不导出的,大写开头的是导出的。
先来看一个例子:

// counters/counters.go
package counters

type alertCounter int

func New(value int) alertCounter {
  return alertCounter(value)
}

// listing68.go
package main

import (
  "fmt"
  "github.com/goinaction/code/chapter5/listing68/counters"
)

func main() {
  counter := counters.New(10)
  fmt.Printf("Counter: %d\n", counter)
}

在counters.go里面的alertCounter是小写字母开头,因此该identifer没有导出,不能在下个文件的main函数里面使用。但是New方法是导出了的。因此,main函数里面的代码没有错误。

但是有个问题,alertCounter没有导出,怎么可以使用呢?
见原文:

This is possible for two reasons. First, identifiers are exported or unexported, not values. Second, the short variable declaration operator is capable of inferring the type and creating a variable of the unexported type. You can never explicitly create a variable of an unexported type, but the short variable declaration operator can.

简单来说:
1,导出或者不导出的是identifiers,不是values。也就是说小写字母的类型的值是可以在package外使用的;
2,不能显式使用未导出的identifier,但是可以隐式使用。也就是使用:=是可以的。

看一个完整版:

// entities/entities.go
package entities

type user struct{
  Name string
  Email string
}

type admin struct{
  user
  Rights int
}

// listing74.go
package main

import (
  "fmt"
  "github.com/goinaction/code/chapter5/listing74/entities"
)

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

推荐阅读更多精彩内容