Golang入门学习之结构体(struct)--练气八层

844 阅读6分钟

写在前面

在上一文《Golang入门学习之函数》当中,我们学习了Golang当中函数的用法。接下来,我们将一起来学习Golang的结构体。

结构体的定义

结构体(struct)类似于其他面向对象当中(class)的概念,但它和类有着明显的不同。结构体是复合类型(composite types),它由一系列属性组成,这些属性对应着不同的值与类型。组成结构体类型的那些数据称为字段(fields).

Golang通过结构体的形式支持用户自定义类型,或者叫定制类型。

定义方式:

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

结构体的实例化

使用new()

我们可以使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针。

var t *StructName
t = new(StructName)

请看下面这个例子:

首先,我们在go_code/struct/model这个包当中,定义了一个结构体Immortal表示修仙者,并在main当中引入这个包,并使用Immortal

go_code/struct/model/immortal.go:

package model

// 修仙者
type Immortal struct {
	Name string
	Age int
	Gender string
}

main中:

package main

import (
	"go_code/struct/model"
	"strconv"
)

func main() {
	// i是指向model.Immortal结构体变量的指针
	var i *model.Immortal
	i= new(model.Immortal)
	i.Age = 500
	i.Name = "韩立"
	i.Gender = "男"

	println(i.Name, strconv.Itoa(i.Age)+"岁",i.Gender)

}

输出:

韩立 500岁 男

我们可以通过点符号获取结构体的值或给其赋值,点符号在Golang中称之为选择器符(selector-notation)。

使用混合字面量语法

初始化一个结构体实例的更简短和惯用的方式如下:

// i的类型是 *model.Immortal
var i =  &model.Immortal{"南宫婉",18,"女"}

// 或者
var i model.Immortal
i := model.Immortal{"南宫婉",18,"女"}

上面这两个例子当中的写法(&model.Immortal{"南宫婉",18,"女"} )称之为混合字面量语法 (composite literal sysntax),这是一种简写,底层仍然会调用new函数。在这种写法当中,需要注意的一点是值的顺序必须按照字段顺序来写。表达式new(Type)&Type()是等价的。

Golang中结构体的特点

内存布局

Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。

递归结构体

结构体类型可以通过引用自身来定义。这在定义链表或二叉树的元素(通常叫节点)时特别有用,此时节点包含指向临近节点的链接(地址)。

package model

type Node struct {
	data string
	next *Node
}

其中,data字段当中存储的是当前节点的数据,next是指向下一个节点的指针

结构体的转换

Go 中的类型转换遵循严格的规则。当为结构体定义了一个 alias 类型时,此结构体类型和它的 alias 类型都有相同的底层类型。

请看下面的这个例子:

number.go

package model

type Number struct {
	Value float32
}

conversion.go:

package main

import "go_code/struct/model"

func main() {
	var a = myNumber{Value: 18.0}
	b := model.Number(a)
	println(b.Value)
}

type myNumber model.Number

使用工厂方法创建结构体实例

Golang不支持其他面向对象编程语言(例如Java)那样的构造方法。我们通常通过定义一个构造工厂方法用于创建结构体实例。

请看下面的例子:

package model

// 修仙者
type Immortal struct {
	Name string
	Age int
	Gender string
}

func NewImmortal(age int, name, gender string) *Immortal {
	if age<0 {
		return nil
	}
	return &Immortal{Name: name,Gender: name,Age: age}
}

对于工厂方法的命名,我们一般以Newnew开头,它返回一个指向结构体实例的指针

如何强制使用工厂方法而不是直接new?

在讲这个问题之前,需要给大家补充一个知识点,那就是Golang当中包的可见性规则:

在Golang中,当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。

因此,为了达到我们的目的,我们需要这样去定义我们的结构体:

package v2

type immortal struct {
	name string
	age int
	gender string
}

func NewImmortal(age int, name, gender string) *immortal {
	if age < 0 {
		return nil
	}
	
	return &immortal{
		name:   name,
		age:    age,
		gender: gender,
	}
}

带标签的结构体

结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包 reflect 能获取它。在gorm(一个Golng的orm框架,类似于Java当中的mybatis)当中使用tag标记字段,从而起到映射数据库表字段的作用。

请看下面这个例子:

package tag

import (
	"fmt"
	"reflect"
)

type Immortal struct {
	Name string "休仙者的名字"
	Age int "修仙者的年龄"
	Gender string "修仙者的性别"
}


func PrintTag(im Immortal, i int) {
	imm :=reflect.TypeOf(im)
	value :=imm.Field(i)
	fmt.Println(value.Tag)
}
package main

import "go_code/struct/model/tag"

func main() {
	var immortal = tag.Immortal{Name: "南宫婉", Age: 18, Gender: "女"}
	for i:=0; i<3; i++ {
		tag.PrintTag(immortal,i)
	}
}

输出:

休仙者的名字
修仙者的年龄
修仙者的性别

匿名字段和内嵌结构体

结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体。在Golang当中,通过组合来实现其他面向对象编程语言当中的继承。

请看下面的例子

package main

import "fmt"

type immortal struct {
	// 姓名
	string
	// 年龄
	int
	// 修仙境界
	level

}

type level struct {
	// 境界名称
	string
	// 灵气值
	float32
}

func main() {
	var im = immortal{
		string: "韩立",
		int:    500,
		level:  level{
			"练气七层",
			7800.0,
		},
	}
	fmt.Println("======修仙者资料卡======")
	fmt.Println("姓名:",im.string)
	fmt.Println("年龄:",im.int)
	fmt.Println("------境界信息---------")
	fmt.Println("境界名称:",im.level.string)
	fmt.Println("境界灵气值:",im.level.float32)
	fmt.Println("------境界信息---------")
	fmt.Println("======修仙者资料卡======")
}

输出:

======修仙者资料卡======
姓名: 韩立
年龄: 500
------境界信息---------
境界名称: 练气七层
境界灵气值: 7800
------境界信息---------
======修仙者资料卡======

写在最后

关于Golang结构体的学习就写到这里,本文当中涉及到的例子可以点击此处下载。如果我的学习笔记能够给你带来帮助,还请多多点赞鼓励。文章如有错漏之处还请各位小伙伴帮忙斧正。