零基础 go - 48(结构体)

1 阅读6分钟

一、go的面向对象编程特性

  • go 支持面向对象编程(OOP),但是和传统的面向对象编程有区别,所以更确切的说go支持面向对象编程特性

  • go没有类的概念,但有结构体(struct)和接口(interface),通过这两者可以实现面向对象编程的特性,如封装、继承和多态

  • go的面向对象对象编程非常简洁,去掉传统的继承、方法重载、构造函数、析构函数、隐藏的this指针等复杂特性,使得面向对象编程更简单易用

  • go没有extends、implements等关键字,结构体和接口之间的关系是隐式的,只要结构体实现了接口定义的方法,就自动满足了接口的要求,这种设计使得代码更加灵活和简洁

二、结构体和结构体变量关系图

image.png

  • 结构体代表了一种数据类型,定义了一组字段和方法,可以看作是一个模板或者蓝图

  • 结构体变量是结构体类型的实例,包含了结构体定义的字段和方法,可以通过结构体变量来访问和操作这些字段和方法

三、结构体变量在内存中的存储方式

image.png

四、声明结构体

type 结构体名 struct {

    字段1 数据类型

    字段2 数据类型

    ...

}

例如

type Person struct {

    Name string

    Age int

    sex string

}
var p Person = Person{Name: "Bob", Age: 25, sex: "male"}

  • 结构体的字段可以是任意类型,包括基本类型、数组、切片、映射、函数、接口等

  • 结构体的字段也可以是匿名字段,即直接在结构体中嵌入另一个结构体,这样就可以实现类似于继承的效果

  • 字段的命名遵循驼峰命名法,首字母大写表示可导出(public),首字母小写表示不可导出(private)

  • 字段类型可以是任意合法的go类型,包括自定义类型和内置类型

  • 创建一个结构体变量后,如果没有给字段赋值,则字段会被自动初始化为该类型的零值,例如数值类型为0,字符串类型为"",布尔类型为false,指针类型为nil等

  • slice、map、channel等引用类型的字段在结构体变量创建时会被初始化为nil,需要在使用前进行初始化,否则会导致运行时错误

  • slice 、map 、channel使用前都需要使用make函数进行初始化

  • 结构体变量是值类型,同一结构体的不同变量之间是相互独立的,修改一个变量的字段不会影响另一个变量的字段

  • 如果同一个结构体的不同变量,一个变量的改变需要反映到另一个变量上,可以使用指针类型的字段,或者将结构体变量定义为指针类型,这样就可以通过指针来共享数据


package main

import "fmt"

type S struct {

    A int

    B string

    C bool

    D []int

    E map[string]int

    F chan int

}

func main() {

    s := S{}

    fmt.Printf("%+v\n", s) // 输出:{A:0 B: C:false D:[] E:map[] F:chan int(nil)}

    if(s.D == nil) {

        fmt.Println("slice D is nil") // 输出:slice D is nil

    }

    if(s.E == nil) {

        fmt.Println("map E is nil") // 输出:map E is nil

    }

    if(s.F == nil) {

        fmt.Println("channel F is nil") // 输出:channel F is nil

    }




    // 需要使用make函数初始化slice、map、channel等引用类型的字段

    s.D = make([]int, 0)

    s.E = make(map[string]int)

    s.F = make(chan int)

    s.D = append(s.D, 1, 2, 3)

    s.E["key"] = 42

    go func() {

        s.F <- 100

    }()

    fmt.Printf("%+v\n", s) // 输出:{A:0 B: C:false D:[1 2 3] E:map[key:42] F:0xc0000a2000}

    fmt.Println(<-s.F) // 输出:100





    // 结构体变量是值类型,同一结构体的不同变量之间是相互独立的

    s1 := S{A: 10, B: "hello"}

    s2 := s1 // 复制s1的值给s2,s1和s2是两个独立的变量

    s2.A = 20 // 修改s2的字段A,不会影响s1的字段A

    fmt.Printf("s1: %+v\n", s1) // 输出:s1: {A:10 B:hello C:false D:[] E:map[] F:chan int(nil)}

    fmt.Printf("s2: %+v\n", s2) // 输出:s2: {A:20 B:hello C:false D:[] E:map[] F:chan int(nil)}




    // 使用指针类型的字段,或者将结构体变量定义为指针类型,可以通过指针来共享数据

    s3 := &s1 // 定义s3为指向s1的指针变量,s3和s1指向同一个结构体实例

    s3.A = 30 // 修改s3的字段A,会影响s1的字段A,因为s3和s1指向同一个结构体实例

    fmt.Printf("s1: %+v\n", s1) // 输出:s1: {A:30 B:hello C:false D:[] E:map[] F:chan int(nil)}

    fmt.Printf("s3: %+v\n", s3) // 输出:s3: &{A:30 B:hello C:false D:[] E:map[] F:chan int(nil)}

}

五、创建结构体变量和访问结构体

创建方式

4和5方式返回的是结构体指针变量,1、2、3方式返回的是结构体变量

当使用结构体指针方式创建时,(*p).name和p.name是等价的,go允许直接使用p.name的语法糖来访问指针变量的字段

  • 1、先声明后赋值

var 变量名 结构体类型

例如

var p Person

p.Name = "David"

p.Age = 28

p.sex = "male"

  • 2、直接声明并初始化

var 变量名 结构体类型 = 结构体类型{字段1:值1, 字段2:值2, ...}

例如

var p Person = Person{Name: "Alice", Age: 30, sex: "female"}

p.Name = "David"

  • 3、使用类型推断创建

变量名 := 结构体类型{字段1:值1, 字段2:值2, ...}

例如

p := Person{Name: "Bob", Age: 25, sex: "male"}

p.Name = "David"

  • 4、创建结构体指针变量(这种方式返回的结构体指针)

var 变量名 *结构体类型 = &结构体类型{字段1:值1, 字段2:值2, ...}

例如

var p *Person = &Person{Name: "Charlie", Age: 35, sex: "male"}

p.Name = "David" // 也可以写成 (*p).Name = "David",但go允许直接使用p.Name的语法糖来访问指针变量的字段

  • 5、使用new函数创建结构体指针变量(这种方式返回的结构体指针)

变量名 := new(结构体类型)

例如

// var p *Person = new(Person) // 等价如下。创建一个Person类型的结构体指针变量,字段会被自动初始化为零值

p := new(Person) // 创建一个Person类型的结构体指针变量,字段会被自动初始化为零值

p.Name = "David" // 也可以写成 (*p).Name = "David",但go允许直接使用p.Name的语法糖来访问指针变量的字段

p.Age = 28

p.sex = "male"

六、结构体类型的内存分配机制

image.png

image.png

image.png

七、结构体使用的注意事项

  • 结构体中的字段在内存中是连续分布的,这使得结构体变量在内存中的访问效率较高

image.png

image.png

image.png

  • 结构体变量是值类型,传递结构体变量会进行值复制,如果结构体较大或者需要共享数据,建议使用结构体指针变量

  • xxxx