zoukankan      html  css  js  c++  java
  • [golang note] 类型系统

    值和引用


    • 值语义和引用语义

           值语义和引用语义的差别在于赋值:

    b = a
    b.Modify()

           如果b的修改不会影响a的值,那么属于值类型,否则属于引用类型。

    • 值类型和引用类型

            引用类型一个特点:引用不绑定特定对象(c++中引用是要绑定特定对象),例如有两个同类型引用a和b,它们可以引用各自的对象A和B;但如果a和b的引用都指向A,那么通过b修改对象内容可以反应到a引用之中。

           √ golang从本质上说,一切皆是值类型,并没有内建一个类似java或c#等语言中的reference类型。 

            golang可以使用指针,可以在行为上达到类似java或c#等语言中reference的效果。

            golang中从行为表现上看,数组属于值类型数组切片、字典、通道和接口属于引用类型

    ▶  引用类型实现

            golang中通过指针能实现引用类型效果

            golang中通过指针或对象访问成员都是使用点操作符,因此指针看起来和对象一样,即类似引用。

    package main
    
    import (
        "fmt"
    )
    
    type Rect struct {
        width, height float64
    }
    
    func main() {
        var a Rect = Rect{100, 200}
        var b *Rect = &a
    
        b.width = 300
        b.height = 400
    
        fmt.Println(a.width, a.height) // 300 400
    }

    ▶  数组

            golang中从行为表现上看数组属于值类型

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var a [3]int = [3]int{1, 2, 3}
        var b [3]int = a
    
        b[1]++
    
        fmt.Println(a) // [1 2 3]
        fmt.Println(b) // [1 3 3]
    }

    ▶ 数组切片

            我们大致可以将数组切片[]T抽象表示为:

    type slice struct {
        first *T
        len int
        cap int
    }

            golang数组切片本质上是一个含有存储指针的结构体,因此本质上说它是值类型,但从表现行为上看是一个引用类型

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var a []int = []int{1, 2, 3}
        var b []int = a
    
        b[1]++
    
        fmt.Println(a) // [1 3 3]
        fmt.Println(b) // [1 3 3]
    }

    ▶ 字典类型

            我们大致可以将字典map[K]V抽象为:

    type Map_K_V struct {
        // ...
    }
    
    type map[K]V struct {
        impl *Map_K_V
    }

            golang字典本质上是一个含有字典指针的结构体,因此本质上说它是值类型,但从表现行为上看是一个引用类型

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var a, b map[string]string
        a = make(map[string]string)
        a["1"] = "haha"
        a["2"] = "hehe"
    
        b = a
        b["2"] = "shit"
    
        fmt.Println(a) // map[1:haha 2:shit]
        fmt.Println(b) // map[1:haha 2:shit]
    }

    ▶ 通道和接口

            与数组切片和字典类似,通道和接口本质上说值类型,但从行为表现上看属于引用类型

    结构体(struct)


    • 结构体定义

           golang中的结构体(struct)和其他语言中的类(class)有同等地位。不同的是,golang放弃了包括继承在内的大量面向对象特性,只保留了组合这个最基础特性。

    ▶ 语法如下

    type 结构体名 struct {
        // 成员定义
    }

    • 构造和初始化

    ▶ new方式

    ▪ new函数原型

           http://godoc.golangtc.com/pkg/builtin/#new

    func new(Type) *Type {
        ...
    }

    ▪ 语法如下

    // 创建对象实例
    p := new(Type)
    
    // 初始化代码
    p.xxxx = xxxx
    p.xxxx = xxxx
    ...

    ▪ 示例如下

    package main
    
    import (
        "fmt"
    )
    
    type Rect struct {
        width, height float64
    }
    
    func main() {
        rect := new(Rect)
    
        rect.width = 100
        rect.height = 200
    
        fmt.Println(rect.width, rect.height)
    }

    ▶ { }方式

    ▪ 语法如下

    // 对象类型
    obj0 := Type{}
    obj1 := Type{v1, v2, ..., vn}
    obj2 := Type{k1: v1, k2: v2, ..., kn: vn}
    
    // 指针类型
    ptr0 := &Type{}
    ptr1 := &Type{v1, v2, ..., vn}
    ptr2 := &Type{k1: v1, k2: v2, ..., kn: vn}

            在golang中,未进行显式初始化的变量将会被初始化为该类型的“零”值(bool:false, int:0, string:"")。

            在golang中,没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成。

    ▪ 示例如下

    package main
    
    import (
        "fmt"
    )
    
    type Rect struct {
        width, height float64
    }
    
    func main() {
        // 对象
        rect0 := Rect{}
        fmt.Println(rect0.width, rect0.height) // 0, 0
    
        rect1 := Rect{100, 200}
        fmt.Println(rect1.width, rect1.height) // 100, 200
    
        //rect2 := Rect{100} // error : too few values in struct initializer
    
        rect3 := Rect{ 100, height: 200}
        fmt.Println(rect3.width, rect3.height) // 100, 200
    
        rect4 := Rect{height: 200}
        fmt.Println(rect4.width, rect4.height) // 0, 200
    
        // 指针
        rect5 := &Rect{}
        fmt.Println(rect5.width, rect5.height) // 0, 0
    
        rect6 := &Rect{100, 200}
        fmt.Println(rect6.width, rect6.height) // 100, 200
    
        //rect7 := &Rect{100} // error : too few values in struct initializer
    
        rect8 := &Rect{ 100, height: 200}
        fmt.Println(rect8.width, rect8.height) // 100, 200
    
        rect9 := &Rect{height: 200}
        fmt.Println(rect9.width, rect9.height) // 0, 200
    }

    • 指针的作用

            在golang中,结构体属于值类型,这就意味着跨函数传递某个对象将只能得到其副本,倘若要在另一个函数中修改对象的内容,那么结果只是修改了副本内容,原对象的内容将没有改变

    package main
    
    import (
        "fmt"
    )
    
    type Rect struct {
        width, height float64
    }
    
    func ModifyRect(r Rect) {
        r.width = 1000
        r.height = 1000
        return
    }
    
    func main() {
        rect := Rect{100, 200}
        ModifyRect(rect)
        fmt.Println(rect.width, rect.height) // 100, 200
    }

            只有通过传递指针,才能跨函数修改对象内容。而由于访问对象成员无论是实例还是指针,使用的都是点操作符,这就带来了类型的隐蔽性,因此在创建对象和传递实例时,尽量使用对象指针。

    package main
    
    import (
        "fmt"
    )
    
    type Rect struct {
        width, height float64
    }
    
    func ModifyRect(r *Rect) {
        r.width = 1000
        r.height = 1000
        return
    }
    
    func main() {
        rect := &Rect{100, 200}
        ModifyRect(rect)
        fmt.Println(rect.width, rect.height) // 1000, 1000
    }

    • 添加成员方法

            在golang中,可以给任意类型(包括内置类型,但不包括指针类型)添加相应的成员方法。

            在golang中,没有隐藏的this或self指针,即方法施加的目标对象将被显式传递,没有被隐藏起来。

            在golang中,成员对象定义时候指定作用的目标对象是对象实例还是对象指针。

            在golang中,无论是对象实例还是对象指针都可以调用成员函数,不管成员函数作用对象类型,但是不影响最后的结果,即结果只由作用的目标类型决定(是对象实例还是对象指针)。

    ▶ 对象实例传递

            对象实例传递,即非指针,按值传递。

            这种方式将无法在方法中修改对象实例的内容,因为按值传递得到是对象副本,这与函数参数性质一样。

    ▪ 语法如下

    func (obj Type) 函数名 (参数列表) (返回值列表) {
        // 函数体
    }

    ▪ 示例如下

    package main
    
    import (
        "fmt"
    )
    
    type Rect struct {
        width, height float64
    }
    
    func (r Rect) ModifyRect() {
        r.width = 1000
        r.height = 1000
        fmt.Println(r.width, r.height) // 1000, 1000
    }
    
    func main() {
        rect1 := Rect{100, 200}
        rect1.ModifyRect()
        fmt.Println(rect1.width, rect1.height) // 100, 200

        rect2 := &Rect{100, 200}
        rect2.ModifyRect() //指针依然可以调用,但不改变结果
        fmt.Println(rect2.width, rect2.height) // 100, 200
    }

    ▶ 对象指针传递

    ▪ 语法如下

            对象指针传递,可以修改作用对象的内容。

    func (ptr *Type) 函数名 (参数列表) (返回值列表) {
        // 函数体
    }

    ▪ 示例如下

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type Rect struct {
        width, height float64
    }
    
    func (r *Rect) ModifyRect() {
        r.width = 1000
        r.height = 1000
        fmt.Println(r.width, r.height) // 1000, 1000
    }
    
    func main() {
        rect1 := Rect{100, 200}
        fmt.Println(reflect.ValueOf(rect1).Type()) // main.Rect
        rect1.ModifyRect()                         // 使用对象实例调用依然有效
        fmt.Println(rect1.width, rect1.height)     // 1000, 1000
    
        rect2 := &Rect{100, 200}
        fmt.Println(reflect.ValueOf(rect2).Type()) // *main.Rect
        rect2.ModifyRect()                         // 使用对象指针调用依然有效
        fmt.Println(rect2.width, rect2.height)     // 1000, 1000
    }

    ▶ 为已有类型添加成员方法

            golang有个很酷的特性:可以通过给已有类型Type起一个别名Alias,然后为Alias增加一些新的方法使其成为一个新的类型,同时Alias将完全拥有Type的所有方法。

    ▪ 语法如下

    type Alias Type
    
    func (a Alias) 函数名(参数列表) (返回值列表) {
        // 函数体
    }
    
    func (a *Alias) 函数名(参数列表) (返回值列表) {
        // 函数体
    }

    ▪ 示例如下

    package main
    
    import "fmt"
    
    type Rect struct {
        width, height float64
    }
    
    func (r *Rect) ModifyRect() {
        r.width = 1000
        r.height = 1000
    }
    
    type MyRect Rect
    
    func (r *MyRect) GetArea() float64 {
        return r.width * r.height
    }
    
    func main() {
        rect1 := &Rect{100, 200}
        rect1.ModifyRect()
        fmt.Println(rect1.width, rect1.height)  // 1000 1000
        fmt.Println((*MyRect)(rect1).GetArea()) // 1e+06 进行强制类型转换可以调用
        // fmt.Println(rect1.GetArea())         // error : rect1.GetArea undefined (type Rect has no field or method GetArea)
    
        rect2 := &MyRect{100, 200}
        // rect2.ModifyRect()                  // error : rect2.ModifyRect undefined (type *MyRect has no field or method ModifyRect)
        (*Rect)(rect2).ModifyRect()            // golang是强类型语言,这里调用要进行强制类型转换才能调用
        fmt.Println(rect2.width, rect2.height) // 1000 1000
        fmt.Println(rect2.GetArea())           // 1e+06
    }

    • 包可见性

            golang要使某个符号对其他包可见,只需将该符号定义为以大写字母开头即可。这规则对于成员变量和成员函数是一致的。

    ▶ 示例如下

    ▪ src/rect/rect.go

    package rect
    
    type Rect struct {
        Width, Height, area float64
    }
    
    func (rect *Rect) CalcArea() {
        rect.area = rect.Width * rect.Height
    }
    
    func (rect *Rect) GetArea() float64 {
        return rect.area
    }

    ▪ src/main/main.go

    package main
    
    import "fmt"
    import "rect"
    
    func main() {
        rect := &rect.Rect{Width: 100, Height: 200}
        fmt.Println(rect.Width, rect.Height) // 100 200
    
        rect.CalcArea()
        // fmt.Println(rect.area)   // error : implicit assignment of unexported field 'area' in rect.Rect literal
        fmt.Println(rect.GetArea()) // 20000
    }
  • 相关阅读:
    ReentrantLock的实现语义与使用场景
    队列同步器详解
    设计模式--模板方法模式
    Synchronized及其实现原理
    JAVA线程基础
    JAVA内存模型
    java 线上问题定位工具
    JMX超详细解读
    Hexo
    [转]html5 video在安卓大部分浏览器包括微信最顶层的问题
  • 原文地址:https://www.cnblogs.com/heartchord/p/5241656.html
Copyright © 2011-2022 走看看