欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

golang学习笔记4——结构体

程序员文章站 2022-04-27 08:39:45
...

结构体格式

golang中的结构体格式如下:

type 结构体名称 struct {
	字段名 字段类型
	字段名 字段类型
}

下面定义一个结构体Point,有坐标x, y两个整型字段:

type Point struct {
	x int
	y int
}

同种类型的字段可以写在一行,如下代码:

type Color struct {
	r, g, b byte
}

结构体的初始化

结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存。因此必须在定义结构体并实例化后才能使用结构体的字段。

如下代码所示:

package main

import "fmt"

type Point struct {
	x int
	y int
}

func main() {
	var p Point  // 或者 p := Point{}
	p.x = 1
	p.y = 0
	// 打印:{1 0}
	fmt.Println(p)
}

可以直接使用.来访问结构体的成员变量。

除了使用上面的方式声明并初始化结构体外,还可以使用new关键字,如下代码所示:

package main

import "fmt"

type Point struct {
	x int
	y int
}

func main() {
	p := new(Point)  // 也可以使用 p := &Point{}
	p.x = 1
	p.y = 0
	fmt.Println(p)
}

var p Point这种方式不同的是,使用new关键字创建的是Point类型的指针变量,golang中同样可以使用.来访问结构体指针的成员变量。

也可以在声明结构体变量时直接初始化:

func main() {
	p := Point{
		x: 1,
		y: 0,
	}
	fmt.Println(p)
}

下面的代码中定义了两个结构体:猫和人,人养了一只宠物,代码如下:

package main

import "fmt"

// 结构体 猫
type Cat struct {
	Name   string
	Weight float32
	Color  string
}

// 结构体 人
type Person struct {
	Name string
	Age  int
	// 养了一只猫
	Pet  *Cat
}

func main() {
	// 初始化一只猫的指针
	pet := &Cat{
		Name:   "kenny",
		Weight: 20.3,
		Color:  "white",
	}
	// 初始化一个人
	person := Person{
		Name: "zhangsan",
		Age:  25,
		Pet:  pet,
	}
	fmt.Println(person)
}

匿名结构体

// 结构体作为函数参数
func test(msg *struct {
	ID   string
	Name string
	Age  int
}) {
	fmt.Printf("id: %v, name: %v, age: %v", msg.ID, msg.Name, msg.Age)
}

func main() {
	// 调用函数,传入匿名的结构体
	test(&struct {
		ID   string
		Name string
		Age  int
	}{
		"asdfasdf",
		"wangwu",
		25,
	})
	// 打印结果:id: asdfasdf, name: wangwu, age: 25
}

模拟构造函数

golang没有Java中的类的概念,自然也没有类的构造方法,但是可以模拟结构体的构造函数,比如如下代码:

// 结构体: 猫
type Cat struct {
	Name  string
	Color string
}

// 模拟猫的构造函数,传入姓名
func NewCatWithName(name string) *Cat {
	return &Cat{
		Name: name,
	}
}

// 模拟猫的构造函数,传入猫的颜色
func NewCatWithColor(c string) *Cat {
	return &Cat{
		Color: c,
	}
}

func main() {
	// 调用“构造函数”创建两个猫
	c1 := NewCatWithColor("black")
	c2 := NewCatWithName("kenny")
	fmt.Println(c1, c2)
}

模拟继承

golang中在一个结构体中嵌入另一个结构体,来完成类似于面向对象编程中的类的继承,如下代码:

// 结构体 表示动物
type Animal struct {
	Color string
}

// 结构体 猫
type Cat struct {
	Animal  // 这里嵌入了Animal这个结构体,类似于Cat继承了Animal
	Name string
}

func main() {
	cat := Cat{}
	// Cat类中没有声明Color,但是由于嵌入了Animal,所以也有了Color字段
	cat.Color = "black"
	cat.Name = "haha"
	fmt.Println(cat)
}

给结构体添加方法

在面向对象编程中,我们除了给类添加成员变量,还可以为类添加方法,在golang中定义结构体时,只能定义结构体中的成员变量,要为结构体添加方法,只能使用函数来实现,如下代码:

// 定义结构体 猫
type Cat struct {
	Color string
}

// 为猫添加一个Eat()方法
func (c *Cat) Eat() {
	fmt.Printf("The %s cat is eating...\n", c.Color)
}

func main() {
	cat := &Cat{
		Color: "black",
	}
	// The black cat is eating...
	cat.Eat()
}

上面的代码为猫这个结构体添加了一个Eat方法,在定义Eat函数时,func后面紧接着的是(c *Cat),这个(c *Cat)叫做接收器,表示该函数作用的对象,因为是*Cat,所以表示Eat方法作用于Cat指针上,于是在main函数里,初始化了一个cat指针,就可以直接使用cat.Eat()来调用Eat函数了。

golang里的接收器分为两种:指针类型的接收器和非指针类型的接收器。

上面的代码中展示的是指针类型的接收器,其实也可以定义非指针类型的接收器,如下代码:

// 点,包含两个坐标值
type Point struct {
	X int
	Y int
}

// 非指针类型的接收器
func (p Point) Add(a, b int) Point {
	return Point{
		X: p.X + a,
		Y: p.Y + b,
	}
}

func main() {
	p := Point{
		X: 0,
		Y: 0,
	}
	p1 := p.Add(1, 1)
	fmt.Println(p1)
}

在上面的代码中,AddPoint的一个方法,该方法接受两个整型数据,返回一个PointAdd函数的接收器是一个Point结构体,而非结构体的指针类型。

指针类型接收器与非指针类型接收器的区别:

  • 小对象比较适合使用非指针类型的接收器,因为其复制时速度较快
  • 大对象比较适合使用指针类型的接收器,因为大对象的复制性能较差,使用指针速度更快

给基本数据类型添加方法

// 定义一个MyInt类型(就是int型)
type MyInt int

// 给MyInt型的变量添加一个isZero方法
func (i MyInt) isZero() bool {
	return i == 0
}

func main() {
	var i MyInt = 3
	fmt.Println(i.isZero())
}

方法和函数的统一调用

这里说的方法,指的是与某个结构体关联起来的函数,该函数具有接收器;而普通的使用func定义的,称为函数。下面的代码展示了方法与函数的统一调用:

type Person struct {
}

// 定义Person的sing方法
func (p *Person) sing(song string) {
	fmt.Printf("the person is singing %s\n", song)
}

// 定义一个单独的sing函数
func sing(song string) {
	fmt.Printf("call function sing, the song is %s\n", song)
}

func main() {
	// 定义f
	var f func(string)

	p := &Person{}
	// 将f赋值为Person的成员方法
	f = p.sing

	f("My heart will go on")

	// 将f赋值为独立的函数
	f = sing
	f("My heart will go on")
}

上面的代码证明了,如果方法与函数具有相同的签名,则可以使用与它们签名一致的变量保存该方法或函数。

类型内嵌结构体

golang中的结构体在声明时,可以只写字段类型而不写字段名称,这种结构体称为类型内嵌或匿名字段类型内嵌,如下代码所示:

// 定义Data结构体,该结构体字段只有类型没有名称
type Data struct {
	string
	int
	float32
}

func main() {
	// 初始化结构体
	data := &Data{
		string:  "zhangsan",
		int:     20,
		float32: 80.5,
	}
	// 打印: &{zhangsan 20 80.5}
	fmt.Println(data)
}

结构体的内嵌与初始化

下面以一个例子说明结构体的内嵌,房子由门和窗户组成,如下代码:

// 窗户
type Window struct {
	Width  int
	Height int
}

// 门
type Door struct {
	Width  int
	Height int
}

// 房子
type House struct {
	Window
	Door
}

func main() {
	// 初始化House的方式
	house := &House{
		Window: Window{
			Width:  10,
			Height: 10,
		},
		Door: Door{
			Width:  20,
			Height: 45,
		},
	}
	fmt.Println(house)
}

上面的代码如果把Door这个结构体去掉,而直接把Door的结构写在House里,就成了嵌套的匿名结构体,如下代码:

// 窗户
type Window struct {
	Width  int
	Height int
}

// 房子
type House struct {
	Window
	// 门直接以内嵌形式声明,而不单独声明结构体
	Door struct {
		Width  int
		Height int
	}
}

func main() {
	house := &House{
		Window: Window{
			Width:  10,
			Height: 10,
		},
		// 初始化House时给Door赋值的方法如下:
		Door: struct {
			Width  int
			Height int
		}{
			Width:  20,
			Height: 45,
		},
	}
	fmt.Println(house)
}