Go语言实践编程语言爱好者

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

2019-11-18  本文已影响0人  Mr_Hospital

首先,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具有两种类型:

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

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

这个表的意思是:

从method receiver的角度来看:


image.png

这个的意思就是:

上面的代码的方法是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)
}
上一篇下一篇

猜你喜欢

热点阅读