• Go语言学习笔记—golang指针与结构体


    视频来源:B站《golang入门到项目实战 [2022最新Go语言教程,没有废话,纯干货!]》

    文章为自己整理的学习笔记,侵权即删,谢谢支持!


    一 值类型和引用类型(补充)

    • 值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct

    • 引用类型:指针、slice 切片、map、管道 chan、interface 等都是引用类型

    使用特点:

    1. 值类型:变量直接存储值,内存通常在栈中分配[外链图片转存失败,源站可能有防盗链机制,建议将在这里插入图片描述

    2. 引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由 GC 来回收在这里插入图片描述

    3. 内存的栈区和堆区示意图在这里插入图片描述

    二 golang指针

    1.1 基础知识

    Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。若传递数据使用指针,则无需拷贝数据。

    类型指针不能进行偏移和运算。

    Go语言中的指针操作非常简单,只需要记住两个符号:&(取地址)和*(根据地址取值)。

    每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行取地址操作。Go语言中的值类型(intfloatboolstringarraystruct)都有对应的指针类型,如:*int*int64*string等。

    1.2 语法

    一个指针变量指向了一个值的内存地址。

    也就是我们声明了一个指针之后,可以像变量赋值一样,把一个值的内存地址放入到指针当中。

    类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:

    var var_name *var-type
    
    • 1

    var-type:为指针类型

    var_name:为指针变量名

    *:用于指定变量是作为一个指针。

    1.3 实例演示

    package main
    
    import "fmt"
    
    func main() {
    	var a int = 20 /* 声明实际变量 */
    	var ip *int    /* 声明指针变量 */
    	ip = &a        /* 指针变量的存储地址 */
    	fmt.Printf("a变量的地址是: %x\n", &a)
    	/* 指针变量的存储地址 */
    	fmt.Printf("ip变量储存的指针地址: %x\n", ip)
    	/* 使用指针访问之 */
    	fmt.Printf("*ip变量的值: %d\n", *ip)
    
    	var sp *string
    	var s string = "hello"
    	sp = &s
    	fmt.Printf("sp的数据类型: %T\n", sp)
    	fmt.Printf("sp的值: %v\n", *sp)
    
    	var bp *bool
    	var b bool = true
    	fmt.Printf("bp的数据类型: %T\n", bp)
    	bp = &b
    	fmt.Printf("bp的值: %v\n", *bp)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    运行结果:

    a变量的地址是: c000014098
    ip变量储存的指针地址: c000014098
    *ip变量的值: 20
    sp的数据类型: *string
    sp的值: hello
    bp的数据类型: *bool
    bp的值: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    三 golang指向数组的指针

    Go语言中指向数组的指针与C语言不同

    • C语言指向数组的指针是指向数组的首地址

    • Go语言中指向数组的指针是指向数组里面的每个元素

    3.1 语法

    var ptr [MAX]*int; 
    
    • 1

    表示数据里面的元素的类型是指针类型

    3.2 实例演示

    package main
     
    import "fmt"
     
    const MAX int = 3
     
    func main() {
        a := []int{1, 3, 5}
        var i int
        var ptr [MAX]*int
        fmt.Println(ptr) //这个打印出来的是[<nil> <nil> <nil>]
        for i := 0; i < MAX; i++ {
            ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
        }
        for i = 0; i < MAX; i++ {
            fmt.Printf("a[%d] = %d\n", i, *ptr[i]) //*ptr[i]就是打印出相关指针的值了
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    运行结果:

    [<nil> <nil> <nil>]
    a[0] = 1
    a[1] = 3
    a[2] = 5
    
    • 1
    • 2
    • 3
    • 4

    四 golang类型定义和类型别名

    4.1 go语言类型定义

    语法:

    type NewType Type
    
    • 1

    NewType:定义的新类型

    Type:原类型

    实例:

    package main
     
    import "fmt"
     
    func main() {
        // 类型定义
        type MyInt int
        // i为MyInt类型
        var i MyInt
        i = 100
        fmt.Printf("i: %v i: %T\n", i, i)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果:

    i: 100 i: main.MyInt
    
    • 1

    4.2 go语言类型别名

    语法:

    type NewType = Type
    
    • 1

    NewType:定义的类型别名

    Type:原类型

    实例:

    package main
     
    import "fmt"
     
    func main() {
        // 类型别名定义
        type MyInt2 = int
        // i其实还是int类型
        var i MyInt2
        i = 100
        fmt.Printf("i: %v i: %T\n", i, i)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果:

    i: 100 i: int
    
    • 1

    4.3 go语言类型定义和类型别名的区别

    1. 类型定义相当于定义了一个全新的类型,与之前的类型不同;但是类型别名并没有定义一个新的类型,而是使用一个别名来替换之前的类型
    2. 类型别名只会在代码中存在,在编译完成之后并不会存在该别名
    3. 因为类型别名和原来的类型是一致的,所以原来类型所拥有的方法,类型别名中也可以调用,但是如果是重新定义的一个类型,那么不可以调用之前的任何方法

    五 golang 支持面向对象编程特性

    5.1 看一个问题

    有两只猫:一只名字叫小白,今年3岁,白色;另一只锚叫小花,今年100岁,花色。请编写一个程序当用户输入名子时就显示该猫的名字、年龄和颜色;若输入名字错误时则显示没有这只猫

    5.2 使用现有技术解决

    ① 单独的定义变量解决

    var cat1Name string = "小白"
    var cat1Age int = 3
    var cat1Color string = "白色"
    
    var cat2Name string = "小花"
    var cat2Age int = 100
    var cat2Color string = "花色"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ② 使用数组解决

    var catName [2]string = [...]string{"小白", "小花"}
    var catAge [2]int = [...]int{3, 100}
    var catColor [2]string = [...]string{"白色", "花色"}
    
    • 1
    • 2
    • 3

    5.3 现有技术解决的缺点分析

    1. 使用变量或者数组来解决养猫的问题,不利于数据的管理和维护。因为名字,年龄,颜色都是属于一只猫,但是这里是分开保存。
    2. 如果我们希望对一只猫的属性(名字、年龄,颜色)进行操作(绑定方法), 也不好处理。
    3. 引出我们要讲解的技术——结构体。

    5.4 golang 语言支持面向对象编程特性说明

    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)关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在 Golang 中面向接口编程是非常重要的特性。

    六 golang结构体简介

    6.1 快速入门

    结构体与结构体变量(实例/对象)的关系示意图:

    在这里插入图片描述

    对上图的说明:

    1. 将一类事物的特性提取出来(比如猫类), 形成一个新的数据类型, 就是一个结构体。
    2. 通过这个结构体,我们可以创建多个变量(实例/对象)
    3. 事物可以猫类,也可以是 Person , Fish 或是某个工具类。。。

    用结构体方式解决5.1问题:

    package main
    
    import "fmt"
    
    type Cat struct {
    	Name  string
    	Age   int
    	Color string
    	Hobby string
    }
    
    func main() {
    	var cat1 Cat
    	cat1.Name = "小白"
    	cat1.Age = 3
    	cat1.Color = "白色"
    	cat1.Hobby = "吃河虾"
    
    	fmt.Printf("cat1: %v\n", cat1)
    
    	fmt.Println("猫猫的信息如下:")
    	fmt.Printf("Name: %v\n", cat1.Name)
    	fmt.Printf("Age: %v\n", cat1.Age)
    	fmt.Printf("Color: %v\n", cat1.Color)
    	fmt.Printf("Hobby: %v\n", cat1.Hobby)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    运行结果:

    cat1: {小白 3 白色 吃河虾}
    猫猫的信息如下:
    Name: 小白
    Age: 3
    Color: 白色
    Hobby: 吃河虾
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    6.2 语法

    type struct_variable_type struct {
        member definition;
        member definition;
        ...
        member definition;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    type:结构体定义关键字

    struct_variable_type:结构体类型名称

    struct:结构体定义关键字

    member definition:成员定义

    例如,定义一个人的结构体Person:

    type Person struct {
        id    int
        name  string
        age   int
        email string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    以上我们定义一个Person结构体,有四个成员,来描述一个Person的信息。

    同类型的可以合并到一行,例如:

    type Person struct {
        id, age int
        name, email string
    }
    
    • 1
    • 2
    • 3
    • 4

    6.3 声明一个结构体变量

    声明一个结构体变量和声明一个普通变量相同,例如:

    package main
     
    import "fmt"
     
    type Person struct {
        id, age     int
        name, email string
    }
     
    func main() {
        // 声明一个结构体变量
        // 方式一:
        var tom Person
        fmt.Printf("tom: %v\n", tom)
        // 方式二:
        kite := Person{}
        fmt.Printf("kite: %v\n", kite)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    运行结果:

    tom: {0 0  }
    kite: {0 0  }
    
    • 1
    • 2

    结构体成员在没有赋值之前都是零值

    结构体和结构体变量(实例)的区别和联系:

    1. 结构体是自定义的数据类型,代表一类事物.
    2. 结构体变量(实例)是具体的,实际的,代表一个具体变量

    6.4 访问结构体成员

    可以使用点运算符 . ,来访问结构体成员,例如:

    package main
     
    import "fmt"
     
    func main() {
        type Person struct {
            id, age     int
            name, email string
        }
     
        var tom Person
        tom.id = 1
        tom.name = "tom"
        tom.age = 20
        tom.email = "tom@gmail.com"
        fmt.Printf("tom: %v\n", tom)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行结果如下:

    tom: {1 20 tom tom@gmail.com}
    
    • 1

    6.5 匿名结构体

    如果结构体是临时使用,可以不用起名字,直接使用,例如:

    package main
     
    import "fmt"
     
    func main() {
        // 匿名结构体
        var dog struct {
            id   int
            name string
        }
        dog.id = 1
        dog.name = "花花"
        fmt.Printf("dog: %v\n", dog)
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    七 golang结构体的初始化

    7.1 未初始化的结构体

    未初始化的结构体,成员都是零值

    int 0

    float 0.0

    bool false

    string nil

    指针,slice,和 map 的零值都是 nil

    例如:

    package main
     
    import "fmt"
     
    type Person struct {
        id, age     int
        name, email string
    }
     
    func main() {
        var tom Person
        fmt.Printf("tom: %v\n", tom)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行结果:

    tom: {0 0  }
    
    • 1

    7.2 使用键值对对结构体进行初始化

    package main
     
    import "fmt"
     
    func main() {
        type Person struct {
            id, age     int
            name, email string
        }
     
        kite := Person{
            id:    1,
            name:  "kite",
            age:   20,
            email: "kite@gmail.com",
        }
     
        fmt.Printf("kite: %v\n", kite)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    运行结果:

    kite: {1 20 kite kite@gmail.com}
    
    • 1

    7.3 使用值的列表初始化

    package main
     
    import "fmt"
     
    func main() {
        type Person struct {
            id, age     int
            name, email string
        }
     
        kite := Person{
            1,
            20,
            "kite",
            "kite@gmail.com",
        }
     
        fmt.Printf("kite: %v\n", kite)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    运行结果:

    kite: {1 20 kite kite@gmail.com}
    
    • 1

    注意:

    1. 必须初始化结构体的所有字段。
    2. 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
    3. 该方式不能和键值初始化方式混用。

    7.4 部分成员初始化

    package main
     
    import "fmt"
     
    func main() {
        type Person struct {
            id, age     int
            name, email string
        }
     
        kite := Person{
            id:   1,
            name: "kite",
            // 其他未初始化的结构体为零值
        }
     
        fmt.Printf("kite: %v\n", kite)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    运行结果:

    kite: {1 0 kite }
    
    • 1

    八 golang结构体指针

    结构体指针和普通的变量指针相同,我先来回顾一下普通变量的指针,例如:

    package main
     
    import "fmt"
     
    func main() {
        var name string
        name = "tom"
        // 声明一个p_name 指针类型
        var p_name *string
        // &name 取name地址
        p_name = &name
        fmt.Printf("name: %v\n", name)
        // 输出指针地址
        fmt.Printf("p_name: %v\n", p_name)
        // 输出指针指向的内容的值
        fmt.Printf("*p_name: %v\n", *p_name)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行结果:

    name: tom
    p_name: 0xc000050230
    *p_name: tom
    
    • 1
    • 2
    • 3

    8.1 直接创建结构体指针

    实例演示:

    package main
     
    import "fmt"
     
    func main() {
        type Person struct {
            id   int
            name string
        }
     
        var tom = Person{1, "tom"}
        var p_person *Person	// 创建结构体指针
        p_person = &tom
        fmt.Printf("tom: %v\n", tom)
        fmt.Printf("p_person: %p\n", p_person)
        fmt.Printf("*p_person: %v\n", *p_person)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行结果:

    tom: {1 tom}
    p_person: 0xc000004078
    *p_person: {1 tom}
    
    • 1
    • 2
    • 3

    8.2 使用new关键字创建结构体指针

    我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址

    例如:

    package main
     
    import "fmt"
     
    func main() {
        type Person struct {
            id   int
            name string
        }
     
        var p_person = new(Person)	// 创建结构体指针
        fmt.Printf("p_person: %T\n", p_person)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行结果:

    p_person: *main.Person
    
    • 1

    从运行结果,我们发现p_person为指针类型

    8.3 访问结构体指针成员

    访问结构体指针成员,也使用点运算符 . ,例如:

    package main
     
    import "fmt"
     
    func main() {
        type Person struct {
            id   int
            name string
        }
     
        var p_person = new(Person)
        fmt.Printf("p_person: %T\n", p_person)
     
        p_person.id = 1	// 访问结构体指针成员
        p_person.name = "tom" // 访问结构体指针成员
        fmt.Printf("*p_person: %v\n", *p_person)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行结果

    p_person: *main.Person
    *p_person: {1 tom}
    
    • 1
    • 2

    九 golang结构体作为函数参数

    go结构体可以像普通变量一样,作为函数的参数,传递给函数,这里分为两种情况:

    • 直接传递结构体,这是一个副本(拷贝),在函数内部不会改变外面结构体内容
    • 传递结构体指针,这时在函数内部,能够改变外部结构体内容

    9.1 直接传递结构体

    package main
     
    import "fmt"
     
    type Person struct {
        id   int
        name string
    }
    
    //  拷贝了一份结构体副本
    func showPerson(person Person) {
        person.id = 1
        person.name = "kite"
        fmt.Printf("person: %v\n", person)
    }
     
    func main() {
        person := Person{1, "tom"}
        fmt.Printf("person: %v\n", person)
        fmt.Println("-------------------")
        showPerson(person)
        fmt.Println("-------------------")
        fmt.Printf("person: %v\n", person)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    运行结果:

    person: {1 tom}
    -------------------
    person: {1 kite}
    -------------------
    person: {1 tom}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从运行结果来看,在函数内部不会改变外面结构体内容

    9.2 传递结构体指针

    实例

    package main
     
    import "fmt"
     
    type Person struct {
        id   int
        name string
    }
     
    func showPerson(person *Person) {
        person.id = 1
        person.name = "kite"
        fmt.Printf("person: %v\n", person)
    }
     
    func main() {
        person := Person{1, "tom"}
        fmt.Printf("person: %v\n", person)
        fmt.Println("-------------------")
        showPerson(&person)
        fmt.Println("-------------------")
        fmt.Printf("person: %v\n", person)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    运行结果:

    person: {1 tom}
    -------------------
    person: &{1 kite}
    -------------------
    person: {1 kite}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从运行结果来看,在函数内部改变了外面结构体内容

    十 golang嵌套结构体

    go语言没有面向对象编程思想,也没有继承关系,但是可以通过结构体嵌套来实现这种效果。

    实例演示:假如有一个人Person结构体,这个人还养了一个宠物Dog结构体

    Dog结构体:

    type Dog struct {
        name  string
        color string
        age   int
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Person结构体:

    type person struct {
        dog  Dog
        name string
        age  int
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    访问它们:

    package main
     
    import "fmt"
     
    type Dog struct {
        name  string
        color string
        age   int
    }
     
    type person struct {
        dog  Dog
        name string
        age  int
    }
     
    func main() {
        var tom person
        tom.dog.name = "花花"
        tom.dog.color = "黑白花"
        tom.dog.age = 2
     
        tom.name = "tom"
        tom.age = 20
     
        fmt.Printf("tom: %v\n", tom)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    运行结果

    tom: {{花花 黑白花 2} tom 20}
    
    • 1

    十一 结构体的内存分配机制

    先看下面一段代码:

    package main
    
    import "fmt"
    
    type Person struct {
    	name string
    	age  int
    }
    
    func main() {
    	var p1 Person
    	p1.age = 10
    	p1.name = "小明"
    
    	var p2 Person = p1
    	fmt.Printf("p2.age: %v\n", p2.age)
    
    	p2.name = "tom"
    	fmt.Printf("p2.name: %v\n", p2.name)
    
    	fmt.Printf("p1.name: %v\n", p1.name)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    运行结果:

    p2.age: 10
    p2.name: tom
    p1.name: 小明
    
    • 1
    • 2
    • 3

    我们知道变量总是存在内存中的,那么结构体变量在内存中如何存在?

    示意图:

    在这里插入图片描述

    由此可见,p2是拷贝了一份p1的副本,p1和p2的地址并不相同

    再看一段代码:

    package main
    
    import "fmt"
    
    type Person struct {
    	name string
    	age  int
    }
    
    func main() {
    	var p1 Person
    	p1.age = 10
    	p1.name = "小明"
    
    	var p2 *Person = &p1 // 内存如下图分析
    
    	fmt.Println((*p2).age)
    	fmt.Println(p2.age)
    
    	fmt.Println("----------")
    
    	p2.name = "tom~"
    	fmt.Printf("p2.name: %v\n", p2.name)
    	fmt.Printf("p1.name: %v\n", p1.name)
    
    	fmt.Println("----------")
    
    	fmt.Printf("*p2.name: %v\n", (*p2).name)
    	fmt.Printf("p1.name: %v\n", p1.name)
    
    	fmt.Println("----------")
    
    	fmt.Printf("p1的地址是: %p\n", &p1)
    	fmt.Printf("p2的地址是: %p\n", &p2)
    	fmt.Printf("p2的值是: %p\n", p2)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    运行结果:

    10
    10
    ----------
    p2.name: tom~
    p1.name: tom~
    ----------
    *p2.name: tom~
    p1.name: tom~
    ----------
    p1的地址是: 0xc0420023e0
    p2的地址是: 0xc000004028
    p2的值是: 0xc0420023e0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    此代码内存图分析:

    在这里插入图片描述

    十二 结构体使用注意事项和细节

    1. 结构体的所有字段在内存中是连续

      package main
      
      import "fmt"
      
      type Point struct {
      	x int
      	y int
      }
      
      type Rect1 struct {
      	leftUp, rightDown Point
      }
      
      type Rect2 struct {
      	leftUp, rightDown *Point
      }
      
      func main() {
      	r1 := Rect1{Point{1, 2}, Point{3, 4}}
      	// r1有四个int,在内存中是连续分布的
      	// 打印地址
      	fmt.Printf("r1.leftUp.x的地址是: %p\nr1.leftUp.y的地址是: %p\nr1.rightDown.x的地址是: %p\nr1.rightDown.y的地址是: %p\n", &r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y)
      
      	fmt.Println("----------")
      
      	r2 := Rect2{&Point{10, 20}, &Point{30, 40}}
      	// r2有两个*Point类型,这两个*Point类型的本身地址也是连续的
      	// 打印地址
      	fmt.Printf("r2.leftUp的地址是: %p\nr2.rightDown的地址是: %p\n", &r2.leftUp, &r2.rightDown)
      
      	fmt.Println("----------")
      
      	// 他们指向的地址不一定是连续的,这个要看系统在运行时是如何分配的
      	fmt.Printf("r2.leftUp指向的地址是: %p\nr2.rightDown指向的地址是: %p", r2.leftUp, r2.rightDown)
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36

      运行结果:

      r1.leftUp.x的地址是: 0xc000012260
      r1.leftUp.y的地址是: 0xc000012268
      r1.rightDown.x的地址是: 0xc000012270
      r1.rightDown.y的地址是: 0xc000012278
      ----------
      r2.leftUp的地址是: 0xc00004e250
      r2.rightDown的地址是: 0xc00004e258
      ----------
      r2.leftUp指向的地址是: 0xc0000140c0
      r2.rightDown指向的地址是: 0xc0000140d0
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    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)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

      运行结果:

      {0} {0}
      
      • 1
    3. 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转

    4. 结构体的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化。
      序列化使用场景:在这里插入图片描述实例:

      package main
      
      import (
      	"encoding/json"
      	"fmt"
      )
      
      type Monster struct {
      	Name  string `json:"name"` // `json:"name"`就是一个结构体tag
      	Age   int    `json:"age"`
      	Skill string `json:"skill"`
      }
      
      func main() {
      	//
      	monster := Monster{"牛魔王", 500, "芭蕉扇~"}
      
      	//
      	//
      	jsonStr, err := json.Marshal(monster)
      	if err != nil {
      		fmt.Printf("json处理错误: %v\n", err)
      	}
      	fmt.Printf("jsonStr: %v\n", string(jsonStr))
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25

      运行结果:

      jsonStr: {"name":"牛魔王","age":500,"skill":"芭蕉扇~"}
      
      • 1
  • 相关阅读:
    Google Chrome 浏览器以全屏模式打开
    设计模式 -- 建造者模式(Builder Pattern)
    《数据结构》之栈和堆结构及JVM简析
    计算机毕设推荐基于微信小程序的自来水收费系统
    余额宝收益怎么算
    view-design组件使用Vue+Input+Table实现动态搜索值并单选
    快照和镜像
    编程思想介绍
    windows系统docker中将vue项目网站部署在nginx上
    HTTP、HTTPS协议以及抓包工具
  • 原文地址:https://blog.csdn.net/qq_39280718/article/details/125432355