Go 语言的类型系统
Go 语言是一种静态类型的编程语言,这意味着编译器在编译时需要事先知道变量的类型,这样有助于编译器对代码的一些优化,提高执行效率。
5.1 用户定义的类型Go 语言里声明用户自定义类型有两种方式:
- 使用关键字 struct 创建结构类型
- 基于一个已有类型,将其作为新类型的类型说明
5.1.1 使用 struct 关键字声明结构类型
声明一个结构类型
type user struct {
name string
email string
age int
isMale bool
}
使用结构类型声明变量并初始化零值
var bill user
任何时候,创建一个变量并初始化为零值,习惯上是使用关键字 var。如果变量被初始化为非零值,就配合结构字面量和短变量声明操作符来创建变量(即 := )
使用结构字面量来声明一个结构类型的变量
lisa := user{
name: "lisa",
email: "lisa@email.com"
age: 22,
isMale: false,
}
不使用字段名,创建结构类型的值
// 这种形式下,值的顺序很重要,必须要和结构声明中的顺序一致,另外最后也不需要使用 ',' 结尾
// 可以部分初始化,未初始化的部分为零值
lisa := {"lias", "lisa@eamil.com", 22, false}
使用其他结构类型声明字段
type admin struct {
person user
level string
}
使用结构字面量来创建字段的值
fred := admin {
person: user {
name: "fred",
email: "fred@email.com"
age: 22,
isMale: true,
},
level: "super",
}
5.1.2 基于一个已有类型,将其作为新类型的说明
当需要一个可以用已有类型表示的新类型的时候,这种方法会很好用。
基于 int64 声明一个新类型
type Duration int64
5.2 方法
方法能给用户定义的类型添加新的行为。
方法实际上也是函数,只是在声明时,在关键字 func 和方法名之间增加了一个参数。
// Sample program to show how to declare methods and how the Go
// compiler supports them.
package main
import (
"fmt"
)
// user defines a user in the program.
type user struct {
name string
email string
}
// notify implements a method with a value receiver.
func (u user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",
u.name,
u.email)
}
// changeEmail implements a method with a pointer receiver.
func (u *user) changeEmail(email string) {
u.email = email
}
// main is the entry point for the application.
func main() {
// Values of type user can be used to call methods
// declared with a value receiver.
bill := user{"Bill", "bill@email.com"}
bill.notify()
// Pointers of type user can also be used to call methods
// declared with a value receiver.
lisa := &user{"Lisa", "lisa@email.com"}
lisa.notify()
// Values of type user can be used to call methods
// declared with a pointer receiver.
bill.changeEmail("bill@newdomain.com")
bill.notify()
// Pointers of type user can be used to call methods
// declared with a pointer receiver.
lisa.changeEmail("lisa@newdomain.com")
lisa.notify()
}
5.3 类型的本质
类型的本质主要体现在,如果给这个类型增加或删除某个值,是创建一个新值,还是在更改当前值。这也关系到这个类型在函数之间到底时用值传递还是指针传递。
5.3.1 内置类型
内置类型时语言提供的一组类型,比如数值类型、字符串类型、布尔类型和数组等。
这些类型本质上是原始类型,在函数或方法间传递时,传递的是对应值的副本
5.3.2 引用类型
Go 语言里的引用类型有如下几个:切片、映射、通道、接口和函数类型。
这些类型在函数间传递的是指针,共享底层数据结构。
5.3.3 结构类型
结构类型可以用来描述一组数据值,这组值的本质既可以是原始的,也可以是非原始的。
5.4 接口多态是指代码可以根据类型的具体实现采取不同的行为。
如果一个类型实现了某个接口,所有使用这个接口的地方,都可以支持这种类型的值。
5.4.1 标准库
// Sample program to show how to write a simple version of curl using
// the io.Reader and io.Writer interface support.
package main
import (
"fmt"
"io"
"net/http"
"os"
)
// init is called before main.
func init() {
if len(os.Args) != 2 {
fmt.Println("Usage: ./example2 <url>")
os.Exit(-1)
}
}
// main is the entry point for the application.
func main() {
// Get a response from the web server.
r, err := http.Get(os.Args[1])
if err != nil {
fmt.Println(err)
return
}
// Copies from the Body to Stdout.
io.Copy(os.Stdout, r.Body)
if err := r.Body.Close(); err != nil {
fmt.Println(err)
}
}
5.4.2 实现
接口是用来定义类型的行为。这些被定i的行为不由接口直接实现,而是通过方法由用户定义的类型实现。如果用户定义的类型实现了接口的一组方法之后,那么这个用户定义的类型的值就可以赋给这个接口类型的值。
在这种关系里,用户定义的类型通常称为实体类型。
5.4.3 方法集
方法集定义了接口的接收规则。
// Sample program to show how to use an interface in Go.
package main
import (
"fmt"
)
// notifier is an interface that defined notification
// type behavior.
type notifier interface {
notify()
}
// user defines a user in the program.
type user struct {
name string
email string
}
// notify implements a method with a pointer receiver.
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
// main is the entry point for the application.
func main() {
// Create a value of type User and send a notification.
u := user{"Bill", "bill@email.com"}
sendNotification(u)
// ./listing36.go:32: cannot use u (type user) as type
// notifier in argument to sendNotification:
// user does not implement notifier
// (notify method has pointer receiver)
}
// sendNotification accepts values that implement the notifier
// interface and sends notifications.
func sendNotification(n notifier) {
n.notify()
}
上述代码编译不通过的原因在于:
- 如果使用指针接收者来实现了一个接口,那么只有指向那个类型的指针才能够实现对应的接口
- 如果使用值接收者来实现一个接口,那么那个类型的值和指针都能够实现对应的接口
从方法接收者类型的角度来看方法集
methods receivers | values |
---|---|
(t T) | T and *T |
(t *T) | *T |
5.4.4 多态
// Sample program to show how polymorphic behavior with interfaces.
package main
import (
"fmt"
)
// notifier is an interface that defines notification
// type behavior.
type notifier interface {
notify()
}
// user defines a user in the program.
type user struct {
name string
email string
}
// notify implements the notifier interface with a pointer receiver.
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
// admin defines a admin in the program.
type admin struct {
name string
email string
}
// notify implements the notifier interface with a pointer receiver.
func (a *admin) notify() {
fmt.Printf("Sending admin email to %s<%s>\n",
a.name,
a.email)
}
// main is the entry point for the application.
func main() {
// Create a user value and pass it to sendNotification.
bill := user{"Bill", "bill@email.com"}
sendNotification(&bill)
// Create an admin value and pass it to sendNotification.
lisa := admin{"Lisa", "lisa@email.com"}
sendNotification(&lisa)
}
// sendNotification accepts values that implement the notifier
// interface and sends notifications.
func sendNotification(n notifier) {
n.notify()
}
5.5 嵌入类型
嵌入类型是将已有的类型直接声明在新的结构里,从而实现扩展或修改已有类型的行为。被嵌入的类型被称为新的外部类型的内部类型。
要嵌入一个类型,只需要在外部类型中声明之歌类型的名字就可以了。
内部类型的值以及实现的接口都会自动的提升到外部类型,但当外部类型也有相同的值或者实现了相同的结构时,内部类型将不会提升,但此时仍可以通过内部类型的标识来访问。
// Sample program to show what happens when the outer and inner
// type implement the same interface.
package main
import (
"fmt"
)
// notifier is an interface that defined notification
// type behavior.
type notifier interface {
notify()
}
type person struct {
age int
gender string
}
// user defines a user in the program.
type user struct {
person
name string
email string
}
// notify implements a method that can be called via
// a value of type user.
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
// admin represents an admin user with privileges.
type admin struct {
user
level string
age int
}
// notify implements a method that can be called via
// a value of type Admin.
func (a *admin) notify() {
fmt.Printf("Sending admin email to %s<%s>\n",
a.name,
a.email)
}
// main is the entry point for the application.
func main() {
// Create an admin user.
ad := admin{
user: user{
name: "john smith",
email: "john@yahoo.com",
person: person{
age: 24,
gender: "male",
},
},
level: "super",
age: 26,
}
// Send the admin user a notification.
// The embedded inner type's implementation of the
// interface is NOT "promoted" to the outer type.
sendNotification(&ad)
// We can access the inner type's method directly.
ad.user.notify()
// The inner type's method is NOT promoted.
ad.notify()
fmt.Println(ad.user.person.gender)
fmt.Println(ad.person.gender)
fmt.Println(ad.gender)
fmt.Println(ad.user.person.age)
fmt.Println(ad.person.age)
fmt.Println(ad.age)
}
// sendNotification accepts values that implement the notifier
// interface and sends notifications.
func sendNotification(n notifier) {
n.notify()
}
// Sending admin email to john smith<john@yahoo.com>
// Sending user email to john smith<john@yahoo.com>
// Sending admin email to john smith<john@yahoo.com>
// male
// male
// male
// 24
// 24
// 26
5.6 公开或未公开的标识符
Go 语言支持公开或隐藏标识符。
当一个标识符的名字以小写字母开头,那么这个标识符就是未公开的,在包外不可见
当一个标识符的名字以大写字母开头,那么这个标识符就是公开的,在包外可见
5.7 小结- 使用关键字 struct 或通过指定已存在的类型,可以声明用户定义的类型
- 方法提供了一种给用户定义的类型增加行为的方式
- 设计类型时需要确认类型的本质是原始的还是非原始的
- 接口是声明一组行为并支持多态的类型
- 嵌入类型提供了扩展类型的功能,而无需使用继承
- 标识符要么是从包里公开的,要么是在包里未公开的