golang学习笔记4——结构体

结构体格式

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)
}

你可能感兴趣的:(golang)