Go 的结构体

136 阅读4分钟

「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。

结构体

概念

  • 结构体(struct)就是一组字段(field)
  • Go 语言中数组需要存储同一类型的数据,但在结构体中可以为不同字段定义不同的数据类型
  • 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合

语法格式

声明结构体的语法格式

type T struct {
    field1 type1
    field2 type2
    ...
}

简洁版声明

多个字段类型相同时可以这样声明

type T struct {
    a, b int
}

声明结构体变量

variable_name := T{value1, value2...}

声明结构体变量的几个🌰

package main

import "fmt"

type Vertex struct {
	a int
	b int
}

func main() {
	var (
		// 不指定字段,按顺序赋值
		v1 = Vertex{1, 2}
        
		// 指定字段
		v2 = Vertex{a: 3}
        
		// 不传值,默认取 字段类型的默认值
		v3 = Vertex{}
        
		// 指向结构体的指针
		p1 = &Vertex{4, 5}
	)
	fmt.Println(v1, v2, v3, *p1)
}

运行结果

{1 2} {3 0} {0 0} {4 5}

访问字段

  1. 使用点号.来访问字段
  2. 通过结构体指针来访问字段

使用点号.来访问字段

package main

import "fmt"

type Vertex struct {
	a int
	b int
}

func main() {
	vertex := Vertex{
		a: 11,
		b: 22,
	}
	fmt.Println(vertex.a, vertex.b)
}

运行结果

11 22

访问字段的🌰

package main

import "fmt"

type Vertex struct {
	a int
	b int
}

func main() {
	vertex := Vertex{
		a: 11,
		b: 22,
	}
	fmt.Println(vertex)
	
	vertex.a = 1
	vertex.b = 2
	fmt.Println(vertex)
	fmt.Println(vertex.a, vertex.b)
}

运行结果

{11 22}
{1 2}
1 2

结构体指针

声明结构体指针的几种方式

package main

import "fmt"

type Vertex struct {
	a int
	b int
}

func main() {
    // 空指针
	var vv1 *Vertex
	vv2 := &Vertex{
		1, 2,
	}
	fmt.Println(vv1, vv2)

}

运行结果

<nil> &{1 2}

通过结构体指针来访问字段

如果有一个指向结构体的指针 p,那么可以通过(*p).X来访问其字段 X,但这样太啰嗦了,Go 允许使用隐式间接引用,直接写p.X就可以了

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
    
    // p 是指针
	p := &v
    // 通过指针访问字段,修改值
	p.X = 999
	fmt.Println(v)
}

运行结果

{999 2}

结构体的匿名字段

  • 结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字
  • 匿名字段本身可以是一个结构体类型,就是结构体可以内嵌结构体,也可以是所有的内置类型和自定义类型

类比继承

  • 可以粗略地将这个和面向对象语言中的继承概念相比较
  • Go 语言中的继承是通过内嵌或组合来实现的,所以可以说,在 Go 语言中,相比较于继承,组合更受青睐

直接访问匿名字段的🌰

package main

import "fmt"

type inner struct {
	age   int
	money int
}

type outer struct {
	name   string
	others float64
	// 匿名字段
	int
	inner
}

func main() {

	o1 := outer{}
	o1.age = 24
	o1.money = 55
	o1.name = "小菠萝"
	o1.others = 1.1
	o1.int = 60

	fmt.Printf("o1.age is: %d\n", o1.age)
	fmt.Printf("o1.money is: %d\n", o1.money)
	fmt.Printf("o1.name is: %s\n", o1.name)
	fmt.Printf("o1.others is: %f\n", o1.others)
	fmt.Printf("o1.int is: %d\n", o1.int)
}

运行结果

o1.age is: 24
o1.money is: 55
o1.name is: 小菠萝
o1.others is: 1.100000
o1.int is: 60

声明一个有匿名字段的结构体的变量

package main

import "fmt"

type inner struct {
	age   int
	money int
}

type outer struct {
	name   string
	others float64
	// 匿名字段
	int
	inner
}

func main() {
    // 声明一个变量,指定结构体里面的值,嵌套结构体
    o1 := outer{
		"小菠萝", 1.1, 60, inner{
			24, 55,
		},
	}
	fmt.Println("o1 is:", o1)
}

运行结果

o1 is: {小菠萝 1.1 60 {24 55}}

命名冲突

  • 当两个字段拥有相同的名字(可能是继承来的名字)时该怎么办呢?
  • 外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式;
  • 如果相同的名字在同一级别出现了两次,如果这个名字被使用了,将会引发一个错误(不使用没关系)

同一级别出现两次报错的🌰

package main

func main() {
	type A struct{ a int }
	type B struct{ a, b int }

	type C struct {
		// AB 属于同一级
		A
		B
	}
	var c C
    // AB 都有 a 所以报错了
	c.a = 123
	c.b = 456
}

运行结果

./prog.go:12:3: ambiguous selector c.a

歧义选择器c.a,到底是c.A.a还是c.B.a呢?

不报错的🌰

package main

import "fmt"

func main() {
	type A struct{ a int }
	type B struct{ a, b int }

	type C struct {
		a int
		A
		B
	}
	var c C
	c.a = 123
	c.b = 456
	fmt.Println(c)
}

运行结果

{123 {0} {0 456}}

A、B 两个结构体的 a 字段都是取的零值

如果想设置 A、B 里面的 a 字段值呢?

package main

import "fmt"

func main() {
	type A struct{ a int }
	type B struct{ a, b int }

	type C struct {
		a int
		A
		B
	}
	var c C
	c.a = 123
	c.b = 456
	
    // 这样就能访问嵌套结构体的同名字段了
	c.A.a = 999
	c.B.a = 666

	fmt.Println(c)
}

运行结果

{123 {999} {666 456}}