Go 语言的类型系统:
Go 语言是一种静态类型
的编程语言
这意味着,编译器需要在编译时 知晓 程序里每个值的类型。
提前知道 类型信息,编译器就可以 确保 程序合理地使用值。
这有助于 减少潜在的 内存异常和 bug,并且使编译器机会对代码进行一些性能优化,提高执行效率
值的类型
给 编译器 提供两部分信息:
第一部分 规模,需要分配多少内存给这个值(即值的规模)
第二部分 表示,这段内存表示什么
int64 类型的值需要8 字节(64 位),表示一个整数值
float32 类型的值需要4 字节(32 位),表示一个IEEE-754 定义的二进制浮点数
bool 类型的值需要1 字节(8 位),表示布尔值true和false
注意
类型的内部表示 与 编译代码的体系结构 有关
根据编译所在的机器的体系结构,一个int 值的大小可能是8 字节(64 位),也可能是4 字节(32 位)
1 用户定义的类型
Go 语言允许 用户定义类型。
当用户声明一个新类型时,这个声明就 给编译器提供了一个框架,告知必要的内存大小和表示信息
声明后的类型 与 内置类型的 运作方式类似.
当声明变量时,这个变量对应的值总是会被 初始化。这个值要么 用指定的值初始化,要么用 零值
声明自定义类型的方法
(1)使用 关键字struct 创建一个结构类型
结构里 每个字段都会用一个已知类型声明。
这个已知类型 可以是内置类型,也可以是其他 用户定义的类型。
type user struct {
name string
email string
exit int
priviledge bool
}
声明之后就可以使用这个类型进行值的创建
// 声明user 类型的变量
var bill user
(2)另一种声明用户定义的类型的方法是,基于一个已有的类型,将其作为新类型的类型说明 (从内置类型创建出很多更加明确的类型,并赋予更高级的功能)
基于int64 声明一个新类型
type Duration int64
func main() {
var dur Duration
dur = int64(1000)
}
类型int64 的值不能作为类型Duration 的值来用 cannot use int64(1000) (type int64) as type Duration in assignment
Duration 是一种描述时间间隔的类型,单位是纳秒(ns)
声明,初始化一个结构类型的变量 (结构字面量)
// 声明user 类型的变量,并初始化所有字段
lisa := user{
name: "Lisa",
email: "lisa@email.com",
ext: 123,
privileged: true,
}
lisa := user{"Lisa", "lisa@email.com", 123, true} // 不写字段名, 需要按照顺序 写值
使用其他结构类型声明字段 (结构类型嵌套)
// 声明admin 类型的变量
fred := admin{
person: user{
name: "Lisa",
email: "lisa@email.com",
ext: 123,
privileged: true,
},
level: "super",
}
2 方法 (为用户自定义的类型添加新的行为)具有接受者的函数
函数与方法之间的区别:
关键字 func
和函数名之间的参数被称作 接收者
,将函数与接收者的类型绑在一起。
如果一个函数有接收者,这个 函数 就被称为方法
接收者的类型:
值接收者
使用 值的副本 来调用方法
指针接受者
使用 实际值 来调用方法
/ 这个示例程序展示如何声明
// 并使用方法
package main
import (
"fmt"
)
// user 在程序里定义一个用户类型
type user struct {
name string
email string
}
// notify 使用值接收者 实现了一个方法
func (u user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",
u.name,
u.email)
}
// changeEmail 使用指针接收者 实现了一个方法
func (u *user) changeEmail(email string) {
u.email = email
}
// main 是应用程序的入口
func main() {
// user 类型的值可以用来调用
// 使用值接收者声明的方法
bill := user{"Bill", "bill@email.com"}
bill.notify()
// 指向user 类型值的指针也可以用来调用
// 使用值接收者声明的方法
lisa := &user{"Lisa", "lisa@email.com"}
lisa.notify()
// user 类型的值可以用来调用
// 使用指针接收者声明的方法
bill.changeEmail("bill@newdomain.com")
bill.notify()
// 指向user 类型值的指针可以用来调用
// 使用指针接收者声明的方法
lisa.changeEmail("lisa@newdomain.com") // 声明了一个名为lisa 的指针变量
lisa.notify() // notify 操作的是一个副本,只不过这次操作的是从lisa 指针指向的值的副本。
}
3 类型的本质(不同的类型在函数中传递的方式)
内置类型 (基本类型)
当对这些值进行增加或者删除的时候,会创建一个新值。 基于这个结论,当把这些类型的值传递给方法或者函数时,应该 传递一个对应值的副本 字符串(string)就像整数、浮点数和布尔值一样,本质上是一种很原始的数据值,所以在 函数 或 方法 内外传递时,要传递字符串的一份副本
func main() {
var a byte
a = 97
b := isShellSpecialVar(a) // 调用者传入了一个uint8 值的副本,并接受一个返回值true 或者false。
print(b)
}
func isShellSpecialVar(c uint8) bool {
switch c {
case '*', '#', '$', '@', '!', '?', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9':
return true
}
return false
}
引用类型
Go 语言里的引用类型
有如下几个:切片
、映射
、通道
、接口
和 函数类型
(从技术细节上说,字符串也是一种引用类型。)
声明上述类型的变量时,创建的变量 被称作 标头(header)值
, 每个引用类型创建的标头值是包含一个指向底层数据结构的指针
,
每个引用类型还包含一组特的字段,用于管理底层数据结构, 通过 复制来传递一个引用类型的值的副本,本质上就是 在 共享底层数据结构。
结构类型
结构类型可以用来描述一组数据值,这组值的本质即可以是原始的,也可以是非原始的。
大多数情况下,结构类型的本质并不是原始的,而是非原始的。这种情况下,对这个类型的值做增加或者删除的操作应该更改值本身。
当 需要修改值本身时,在程序中其他地方,需要使用 指针 来共享这个值。
是使用值接收者还是指针接收者,不应该由该方法是否修改了接收到的值来决定。这个决策 应该基于 该 类型的本质
4 接口
多态
是指代码可以 根据类型的具体实现 采取不同行为的能力
如果一个类型实现了某个接口,所有使用这个接口的地方,都可以支持这种类型的值
标准库的接口
// 这个示例程序展示bytes.Buffer 也可以
// 用于io.Copy 函数
package main
import (
"bytes"
"fmt"
"io"
"os"
)
// main 是应用程序的入口
func main() {
var b bytes.Buffer // 创建了一个bytes 包里的Buffer 类型的变量b,用于缓冲数据
// 将字符串写入Buffer
b.Write([]byte("Hello")) // 使用Write 方法将字符串Hello 写入这个缓冲区b
// 使用Fprintf 将字符串拼接到Buffer
fmt.Fprintf(&b, "World!") // 调用fmt包里的Fprintf 函数,将第二个字符串追加到缓冲区b 里
/*fmt.Fprintf 函数接受一个io.Writer 类型的接口值作为其第一个参数。由于
bytes.Buffer 类型的指针实现了io.Writer 接口,所以可以将缓存b 传入fmt.Fprintf
函数,并执行追加操作,由于bytes.Buffer 类型的指针也实现了io.Reader 接口, io.Copy 函数可以用于在终端窗口显示缓冲区b 的内容。
// 将Buffer 的内容写到Stdout
io.Copy(os.Stdout, &b) // 再次使用io.Copy 函数,将字符写到终端窗口。
}
接口的多态行为
// 这个示例程序使用接口展示多态行为
package main
import (
"fmt"
)
// notifier 是一个定义了
// 通知类行为的接口
type notifier interface {
notify()
}
// user 在程序里定义一个用户类型
type user struct {
name string
email string
}
// notify 使用指针接收者实现了notifier 接口
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
// admin 定义了程序里的管理员
type admin struct {
name string
email string
}
// notify 使用指针接收者实现了notifier 接口
func (a *admin) notify() {
fmt.Printf("Sending admin email to %s<%s>\n",
a.name,
a.email)
}
// main 是应用程序的入口
func main() {
// 创建一个user 值并传给sendNotification
bill := user{"Bill", "bill@email.com"}
sendNotification(&bill)
// 创建一个admin 值并传给sendNotification
lisa := admin{"Lisa", "lisa@email.com"}
sendNotification(&lisa)
}
// sendNotification 接受一个实现了notifier 接口的值 (多态函数)
// 并发送通知
func sendNotification(n notifier) {
n.notify()
}
嵌入类型 – (代码复用)
嵌入类型是将已有的类型直接声明在新的结构类型里。被嵌入的类型被称为新的 外部类型
的 ` 内部类型`
通过嵌入类型,与 内部类型相关的标识符 会 提升到外部类型
。这些被提升的标识符就像直接声明在外部类型里的标识符一样,也是外部类型的一部分
外部类型也可以 通过声明与内部类型 标识符同名的标识符 来覆盖内部标识符的字段或者方法
。这就是 扩展或者修改已有类型的方法
。
package main
import "fmt"
type notifier interface {
notify()
}
type user struct {
name string
email string
}
type admin struct {
user
level string
}
func (u *user) notify() {
fmt.Printf("sending user email to %s<%s>\n",
u.name,
u.email)
}
func (a *admin) notify() {
fmt.Printf("sending admin email to %s<%s>\n",
a.name,
a.email)
}
func sendEmail(n notifier) {
n.notify()
}
func main() {
ad := admin{
user: user{
name: "john",
email: "john@qq.com",
},
level: "super",
}
sendEmail(&ad) // 内部类型的方法没有被提升
(&ad).notify() // 内部类型的方法没有被提升 因为 外部类型也实现了 notify的方法
(&ad).user.notify() // 直接访问内部类型的方法
}
公开或者未公开的标识符
当一个 标识符的名字以小写字母开头时,这个标识符就是未公开
的,即包外的代码不可见。
如果一个 标识符以大写字母开头,这个标识符就是公开
的,即被包外的代码可见
// entities 包包含系统中
// 与人有关的类型
package entities
// user 在程序里定义一个用户类型
type user struct { // 内部类型是未公开的
Name string
Email string
} // 内部类型里声明的字段依旧是公开的
// Admin 在程序里定义了管理员
type Admin struct {
user // 嵌入的类型未公开
Rights
}
// main 是应用程序的入口
func main() {
// 创建entities 包中的Admin 类型的值
a := entities.Admin{
Rights: ,
}
// 设置未公开的内部类型的
// 公开字段的值
a.Name = "Bill"
a.Email = "bill@email.com" // 内部类型的标识符 提升 到了外部类型,这些 公开的字段也可以通过外部类型的字段的值来访问
fmt.Printf("User: %v\n", a)
}