Go 语言的类型系统


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)
     }

Buy me a 肥仔水!