• Go语言之面向对象编程(一)


    一、Golang中的面向对象特性

    • Golang中也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。
    • Golang中没有类(class),Go语言的结构体(struct)和其它编程语言的类(class)有同等地位,可以理解Golang是基于struct来实现OOP特性的。
    • Golang面向对象编程非常简洁,去掉了传统OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等。
    • Golang仍然有面向对象编程的继承、封装和多态的特性,只是实现的方式和其它OOP语言不一样,比如,Golang没有extends关键字,继承通过匿名字段来实现。
    • Golang面向对象(OOP)很优雅,OOP本身就是语言类型系统的一部分,通过接口(interface)关联,耦合性低,也非常灵活。

    二、结构体变量的创建与属性

    (一)结构体变量基础

    1、什么是结构体与结构体变量

    结构体表示的就是同一类事物的抽象,是自定义的数据类型。结构体变量(实例)是结构体的具体表现。可以将Golang中的结构体比喻成其它语言的类,将结构体变量比喻成其它语言类的对象。

    2、结构体的声明

    • 基本语法
    type 结构体名称 struct {
    
      field1 type
    
      field2 type
    
    }

    例如:

    type User struct {
    
      UserName string
    
      Age int
    
    }

    3、创建结构体变量的四种方式

    • 方式一  var p1 Person 
    package main
    
    import "fmt"
    
    type Person struct {
        Name string
        Age  int
    }
    
    func main() {
        // 方式一 
        var p1 Person
        fmt.Println(p1)
    }
    • 方式二 var p1 Person = Person{}
    package main
    
    import "fmt"
    
    type Person struct {
        Name string
        Age  int
    }
    
    func main() {
        // 方式二/1
        var p1 Person = Person{
            "lily",
            20,
        }
        fmt.Println(p1)
    
        // 方式二/2 可以指定字段名,不依赖顺序传入
        p2 := Person{
            Name: "harry",
            Age: 30,
        }
        fmt.Println(p2)
    
        //方式二/3
        p3 := Person{}
        p3.Name = "alice"
        p3.Age = 36
        fmt.Print(p3)
    
    }
    • 方式三 var p1 *Person = new(Person)
    package main
    
    import "fmt"
    
    type Person struct {
        Name string
        Age  int
    }
    
    func main() {
        // 方式三 var p1 *Person = new(Person)
        var p1 *Person = new(Person)
        /*
            此时p1是一个指针类型,如果赋值需要:
            (*p1).Name = "igon"
            (*p1).Age = 25
            但是,go设计者为了使用方便,已经在底层做了处理,所可以这样写:
            p1.Name = "igon"
            p1.Age = 25
        */
        p1.Name = "igon"
        p1.Age = 25
    
        fmt.Print(*p1) // 指针取值 {igon 25}
    
    }
    • 方式四 p1 *Person = &Person
    package main
    
    import "fmt"
    
    type Person struct {
        Name string
        Age  int
    }
    
    func main() {
        // 方法四 var p1 *Person = &Person
        var p1 *Person = &Person{}
        /*
            p1是一个指针,访问字段的标准方式是(*p1).Name = "egg",
            但是go的设计者为了使用方便在底层进行了处理,所以可以这样写:
            p1.Name = "egg",实际底层会对p1变成(*p1)
        */
        p1.Name = "egg"
        fmt.Println(*p1)
    
    }

    (二)深入理解结构体变量

    • 结构体是数值类型,所以不同结构体之间互不影响
    • 结构体在内存中的存在形式
    • 结构体中的所有字段在内存中是连续的
    • 结构体类型转换
    • 结构体中的tag

    1、结构体是数值类型,所以不同结构体之间互不影响

    结构体字段(field)就是属性,一般是基本数据类型、数组、引用类型等。在创建一个结构体变量后,如果没有给字段赋值,那么字段就是对应类型的默认值。

    布尔类型:false,
    数值类型:0,
    字符串:"",
    数组(与元素类型有关):a [3]int,  则为[0, 0, 0]
    指针、slice、map:nil,表示未分配空间

    比如:

    package main
    
    import "fmt"
    
    type User struct {
        Name string
        Age  int
    }
    
    func main() {
        // 创建结构体变量
        var user User
        fmt.Println(user) // { 0} 分别是空字符串和0
    }

    不同结构体之间互不影响又是如何体现的呢?

    package main
    
    import "fmt"
    
    type User struct {
        Name string
        Age  int
    }
    
    func main() {
        // 创建第一个结构体变量
        var u1 User = User{
            "alily",
            20,
        }
        fmt.Println(u1) // {alily 20}
    
        // 第二个结构体变量,结构体默认是值拷贝,所以u1与u2是两个不同的数据空间
        u2 := u1
        fmt.Println(u2) // {alily 20}
    
        u2.Name = "tomi"
        fmt.Println(u1, u2) // {alily 20} {tomi 20}
    }

    2、结构体在内存中的存在形式

    看到上述的实例,在内存中是如何存在的呢?


    这也能很好的说明结构体是值类型,不同结构体之间是互不干扰的,但是假如想让u2的Name改变,同时影响u1的又当如何呢?

    package main
    
    import "fmt"
    
    type User struct {
        Name string
        Age  int
    }
    
    func main() {
        // 创建第一个结构体变量
        var u1 User = User{
            "alily",
            20,
        }
        fmt.Println(u1) // {alily 20}
    
        // 第二个结构体变量,声明成指针变量
        var u2 *User = &u1
        fmt.Println(*u2) // {alily 20}
    
        u2.Name = "tomi"
        fmt.Println(u1, *u2) // {tomi 20} {tomi 20}
    }

    这与上面的内存示意图完全不同:

     所以,改变u2的值就会改变u1的值,因为u2是一个指针,存储的就是u1的地址。

    3、结构体中的所有字段在内存中是连续的

    package main
    
    import "fmt"
    
    type Point struct {
        x int
        y int
    }
    
    type Rect struct {
        leftUp, rightDown Point
    }
    
    type Rect1 struct {
        leftUp, rightDown *Point
    }
    
    func main() {
    
        r1 := Rect{
            Point{1, 2},
            Point{3, 4},
        }
    
        r2 := Rect1{
            &Point{1, 2},
            &Point{3, 4},
        }
    
        // r1有4个int,在内存中是连续分布的
        fmt.Printf("r1.leftUp.x地址为:%p r1.leftUp.y地址为:%p r1.rightDown.x地址为:%p r1.rightDown.y地址:%p \n",
            &r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y)
        /*
            r1.leftUp.x地址为:0xc00000e200
            r1.leftUp.y地址为:0xc00000e208
            r1.rightDown.x地址为:0xc00000e210
            r1.rightDown.y地址: 0xc00000e218
        */
    
        // r2有2个*Point类型,它们本身的地址也是连续的
        fmt.Printf("r2.leftUp地址为:%p r2.rightDown地址为:%p \n", &r2.leftUp, &r2.rightDown)
        /*
            r2.leftUp地址为:0xc000050240
            r2.rightDown地址为:0xc000050248
        */
    
        // 但是2个*Point类型指向的地址不一定是连续的
        fmt.Printf("r2.leftUp地址为:%p r2.rightDown地址为:%p", r2.leftUp, r2.rightDown)
        /*
            r2.leftUp地址为:0xc0000140b0
            r2.rightDown地址为:0xc0000140c0
        */
    
    }

    字段地址的连续性有助于在内存中快速取值。

    4、结构体类型转换

     结构体是用户单独定义的类型,与其它类型进行转换时需要有完全相同的字段(名称、个数、类型)。

    package main
    
    import "fmt"
    
    type A struct {
        n1 string
    }
    
    type B struct {
        n1 string
    }
    
    func main() {
        // 类型转换
        var a A
        var b B
        a = A(b)
    
        fmt.Println(a, b)
    
    }

    结构体使用type重新取别名,golang中也认为时新的数据类型,但是可以通过类型强制转换。

    5、结构体中的tag

    结构体中的每个字段可以写上一个tag,该tag通过反射机制获取,常见的场景就是序列化和反序列化。

    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    type User struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
    }
    
    func main() {
    
        // 创建一个结构体变量
        user := User{
            "kity",
            23,
        }
        fmt.Println(user)
    
        // 将结构体变量序列化
        jsonStr, err := json.Marshal(user)
        if err != nil {
            fmt.Print(err)
        } else {
            fmt.Println(string(jsonStr)) // {"name":"kity","age":23}
        }
    
    }

    go中字段名如果大写,那么它只能在本包使用,所以如果需要在其它包encoding/json中去序列化需要大写,但是返回给前端的字段一般习惯是小写字段,所以为了解决这个问题,就使用tag的方式。

  • 相关阅读:
    软件工程第八周总结
    一维最大子数组的和续
    程序员修炼之道阅读笔记02
    软件工程第七周总结
    团队软件的NABCD—校园知网
    程序员修炼之道阅读笔记01
    软件项目管理阅读笔记01
    个人作业4 结对开发地铁
    学习进度五
    学习进度四
  • 原文地址:https://www.cnblogs.com/shenjianping/p/15810942.html
Copyright © 2020-2023  润新知