GO学习笔记 之面向对象编程[结构体]

1.结构体的开篇

一个没有结构体的例子:

package main

import "fmt"

func main()  {
    // 1.变量
    var cat01Name string = "cat_hei"
    var cat01Age int = 3
    fmt.Println(cat01Name,cat01Age)
    var cat02Name string = "cat_bai"
    var cat02Age int = 4
    fmt.Println(cat02Name,cat02Age)
    // 2.数组
    var catNames [2]string = [...]string{"hei","bai"}
    var catAges [2]int = [...]int{3,4}
    fmt.Println(catNames,catAges)

    // 
}

使用结构体:结构体对于其他语言就是对象

2.GO语言面向对象编程说明

(1)golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所有我们说Golang支持面向对象编程特性是比较准确的。

(2)Golang没有类(class),GO语言的结构体(struct)和其他语言的类(class)有同等的地位,你可以理解Golang是基于struct来实现OOP特性的。

(3)Golang面向对象编程非常简洁,去掉了传统OOP语言的继承,方法重载,构造函数和析构函数,隐藏的this指针等待

(4)Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其他OOP语言不一样,比如继承:Golang没有extends关键字,继承是通过匿名字段来实现。

(5)Golang面向对象(OOP)很优雅,OOP本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。面向接口编程的思想。

结构体(类)----实例(变量)

package main

import "fmt"

// // 定义一个结构体
type Cat struct{
    Name string 
    Age int
    Color string
}

func main() {
    //使用结构体
    var cat1 Cat   // 使用一个结构体
    cat1.Name = "bai"
    cat1.Age = 2
    cat1.Color = "white"
    // fmt.Println(cat1)
    fmt.Println("Name = ",cat1.Name)
    fmt.Println("Age = ",cat1.Age)
    fmt.Println("Color = ",cat1.Color)

}

1.结构体是自定义的数据类型,代表一类事物。

2.结构体变量(实例)是具体的,实际的,代表一个具体变量。

结构体是一种值类型数据。

声明结构体语法:

type 结构体名称 struct {

filed1 type

field2 type

field3 type

}

3.结构体的字段/属性

(1)基本介绍:

1.从概念上加法:结构体字段=属性=field

2.字段是结构体的一个组成部分,一般是基本数据类型,数组,也是引用类型。

(2)注意事项和细节说明

1.字典声明语法变量,示例: 字段名 字段类型

2.字段的类型可以为:基本类型、数组或引用类型

3.在创建一个结构体变量,如果没有给字段赋值都有对应的默认值。大概如下:

布尔类型是false,数值是0,字符串是””

数组类型默认值和它的元素类型相关,比如 score[3] int则为[0,0,0]

指针,slice和map的默认值是nil,即没有分配空间。

4.结构体的字段是独立的,互不影响,一个结构体变量字段的更改,不影响另外一个。

结构体里使用:数组,slice和map的数据

package main

import "fmt"

// 如果结构体的字段为:指针,slice和map的默认值都是nil
// 如果需要使用这些字段,需要先make才能使用

type Person struct {
    Name string
    Age int
    Scores [5]float64   // 
    ptr *int  // 指针
    Slice []int  // 切片
    Map map[string]string   // map

}

func main(){
    // 定义结构体
    var person1 Person
    fmt.Println(person1)  // { 0 [0 0 0 0 0] <nil> [] map[]} 

    if person1.ptr == nil{
        fmt.Println("ok1")
    } 
    if person1.Slice == nil {
        fmt.Println("ok2")
    }
    if person1.Map == nil {
        fmt.Println("OK3")
    }

    // 使用slice先make
    person1.Slice = make([]int,10)
    person1.Slice[0] = 10
    fmt.Println(person1.Slice)

    // 使用map先make
    person1.Map = make(map[string] string)
    person1.Map["hh"] = "666"
    fmt.Println(person1.Map)
}

结构体里面的属性之间不会相互影响例子:

package main

import "fmt"

type Cat struct{
    Name string
    Age int
}

func main(){

    // 
    var cat1 Cat
    cat1.Name = "bai"
    cat1.Age = 2
    cat2 := cat1
    cat2.Age = 3
    fmt.Println(cat1)
    fmt.Println(cat2)
}

4.创建结构体变量和访问结构体方式

方式1:直接声明

var person01 Person

方式2:{}

p2 := Person{"two",20}

方式3:&

var p3 *Person = new(Person)

方式4:

例子如下:

package main

import "fmt"

type Person struct{
    Name string
    Age int
}

func main(){
    // 方式1:var p1 Person
    var p1 Person
    p1.Name = "one"
    p1.Age = 19
    fmt.Println(p1)

    // 方式2:p2 := Person{"two",20}
    p2 := Person{"two",20}
    fmt.Println(p2)

    // 方式3:
    var p3 *Person = new(Person)   // p3为结构体的指针
    // 因为p3是个指针,因此标准的写法如下:
    (*p3).Name = "Three"
    (*p3).Age = 21
    fmt.Println(p3)
    // 可简化为:因为创建者底层对上面指针做了处理
    // p3.Name = "Three"
    // p3.Age = 21

    // 方式4:var p4 *Person = &Person()\
    // p4为指针
    var p4 *Person = &Person{}  // 也可以直接在{}进行赋值
    (*p4).Name = "four"
    (*p4).Age = 22
    fmt.Println(p4)   // p4为结构体的指针
    fmt.Println(p4.Name)
    fmt.Println(p4.Age)
}

5.结构体的内存分配机制

例:

package main

import "fmt"

type Person struct{
    Name string
    Age int
}

func main(){
    // 以下p1 和 p2 共享一个内存地址
    var p1 Person
    p1.Age = 10
    p1.Name = "小明"
    var p2 *Person = &p1   // p2是一个指针

    fmt.Println((*p2).Age)  //golang中 (*p2) 与 p2相同
    fmt.Println(p2.Age)

    p2.Name = "SIX"
    fmt.Println(p2.Name)
    fmt.Println(p1.Name)
    fmt.Println((*p2).Name)
}

6.结构体的注意事项和使用细节

(1)结构体的所有字段的内存是连续的[例1]

(2)两个结构体间转换必须两个结构体字段相同[例2]

(3)结构体进行type重新定义(相当于取别名),Golang认为是重新定义数据类型,但是互相之间可以强转。[例2]

(4)struct的每个字段,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化。

例子1:

package main

import "fmt"

type Point struct {
    x int
    y int
}

type Rect struct {
    leftUp, rightDown Point
}

type RectPointer struct {
    leftUp, rightDown *Point
}

func main() {
    //
    r1 := Rect{Point{1, 2}, Point{3, 4}}
    // r1 有4个int ,在内存中是连续分布的
    fmt.Println("r1.leftUp.x地址:", &r1.leftUp.x)
    fmt.Println("r1.leftUp.y地址:", &r1.leftUp.y)
    fmt.Println("r1.rightDown.x地址:", &r1.rightDown.x)
    fmt.Println("r1.rightDown.x地址:", &r1.rightDown.y)
    // // 八进制 一个地址八个字节
    /*
        r1.leftUp.x地址: 0xc000054120
        r1.leftUp.y地址: 0xc000054128
        r1.rightDown.x地址: 0xc000054130
        r1.rightDown.x地址: 0xc000054138
    */
    fmt.Println("结构体下面的值为地址的情况")
    r2 := RectPointer{&Point{1, 2},&Point{3, 4}}
    fmt.Println("r2.leftUp.x地址:", &r2.leftUp.x)
    fmt.Println("r2.leftUp.y地址:", &r2.leftUp.y)
    fmt.Println("r2.rightDown.x地址:", &r2.rightDown.x)
    fmt.Println("r2.rightDown.x地址:", &r2.rightDown.y)
    fmt.Println(r2.rightDown.x)  //  x值
}

例2:

package main

import "fmt"

type A struct{
    Num int
}

type B struct{
    Num int
}

func main()  {
    // 如果要在两个结构体间转换必须两个结构体字段相同
    // 字段个数,字段名称,字段类型相同
    var a A
    var b B
    a = A(b)  // 强转
    fmt.Println(a,b)

 例4:

package main

import "fmt"
import "encoding/json"

type Monster struct{
    Name string `json:"name"`   // `json:"name"` 这个就是结构体的标签
    Age int `json:"age"`        // 效果是可以首字母大写的变量可以在json返回小写
    Skill string `json:"skill"`
}

func main(){

    // 1.创建一个Monster变量
    monster := Monster{"ZEOR",25,"coding go"}

    // 2.将monster变量序列化为json格式的字符串
    jsononster,err := json.Marshal(monster)
    if err != nil{
        fmt.Println("json处理错误")
    }
    fmt.Println("jsononster:",string(jsononster))  // jsononster数据类型为bytes需求强转为string
}