go_struct

定义

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体有中一个或多个成员。
type 语句设定了结构体的名称。结构体的格式如下:

type identifier struct {
field1 type1
field2 type2
...
}
如果字段在代码中从来也不会被用到,那么可以命名它为 _
variable_name := identifier {value1, value2...valuen}

结构体的字段可以是任何类型,结构体,函数或者接口

访问结构体成员
如果要访问结构体成员,需要使用点号 (.) 操作符,格式为:”结构体.成员名”。
在 Go 语言中这叫 选择器(selector)。无论变量是一个结构体类型还是一个结构体类型指针,
都使用同样的 选择器符(selector-notation) 来引用结构体的字段

type myStruct struct { i int }
var v myStruct // v是结构体类型变量
var p *myStruct // p是指向一个结构体类型变量的指针
v.i
p.i

结构体作为函数参数
结构体指针

1.按照顺序提供初始化值
P := person{"Tom", 25}

2.通过field:value的方式初始化,这样可以任意顺序
P := person{age:24, name:"Tom"}

3.当然也可以通过new函数分配一个指针,此处P的类型为*person
P := new(person)

声明

type person struct {
name string
age int
}

var P person // P现在就是person类型的变量了
P.name = "Astaxie" // 赋值"Astaxie"给P的name属性.
P.age = 25 // 赋值"25"给变量P的age属性
fmt.Printf("The person's name is %s", P.name) // 访问P的name属性.

//按照顺序提供初始化值
P := person{"Tom", 25}

//通过field:value的方式初始化,这样可以任意顺序
P := person{age:24, name:"Tom"}

//当然也可以通过new函数分配一个指针,此处P的类型为*person
P := new(person)

一个完整的例子:

import "fmt"

// 声明一个新的类型
type person struct {
name string
age int
}

// 比较两个人的年龄,返回年龄大的那个人,并且返回年龄差
// struct也是传值的
func Older(p1, p2 person) (person, int) {
if p1.age>p2.age { // 比较p1和p2这两个人的年龄
return p1, p1.age-p2.age
}
return p2, p2.age-p1.age
}

func main() {
var tom person

// 赋值初始化
tom.name, tom.age = "Tom", 18

// 两个字段都写清楚的初始化
bob := person{age:25, name:"Bob"}

// 按照struct定义顺序初始化值
paul := person{"Paul", 43}

tb_Older, tb_diff := Older(tom, bob)
tp_Older, tp_diff := Older(tom, paul)
bp_Older, bp_diff := Older(bob, paul)

fmt.Printf("Of %s and %s, %s is older by %d years\n",
tom.name, bob.name, tb_Older.name, tb_diff)

fmt.Printf("Of %s and %s, %s is older by %d years\n",
tom.name, paul.name, tp_Older.name, tp_diff)

fmt.Printf("Of %s and %s, %s is older by %d years\n",
bob.name, paul.name, bp_Older.name, bp_diff)
}

结构体的内存布局

Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。不像 Java 中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中,这点和 Go 语言中的指针很像。

结构体拷贝

copy lib

func main() {
a := &A{1, 1, "a", "b"}
aj, _ := json.Marshal(a)
b := new(B)
_ = json.Unmarshal(aj, b)

fmt.Printf("%+v", b)
}

递归结构体

结构体类型可以通过引用自身来定义。这在定义链表或二叉树的元素(通常叫节点)时特别有用,此时节点包含指向临近节点的链接(地址)
链表:
这块的 data 字段用于存放有效数据(比如 float64),su 指针指向后继节点。

type Node struct {
data float64
su *Node
}
双向链表,它有一个前趋节点 pr 和一个后继节点 su
type Node struct {
pr *Node
data float64
su *Node
}

匿名字段

我们上面介绍了如何定义一个 struct,定义的时候是字段名与其类型一一对应,实际上 Go 支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。
当匿名字段是一个 struct 的时候,那么这个 struct 所拥有的全部字段都被隐式地引入了当前定义的这个 struct。

import "fmt"

type Human struct {
name string
age int
weight int
}

type Student struct {
Human // 匿名字段,那么默认Student就包含了Human的所有字段
speciality string
}

func main() {
// 我们初始化一个学生
mark := Student{Human{"Mark", 25, 120}, "Computer Science"}

// 我们访问相应的字段
fmt.Println("His name is ", mark.name)
fmt.Println("His age is ", mark.age)
fmt.Println("His weight is ", mark.weight)
fmt.Println("His speciality is ", mark.speciality)
// 修改对应的备注信息
mark.speciality = "AI"
fmt.Println("Mark changed his speciality")
fmt.Println("His speciality is ", mark.speciality)
// 修改他的年龄信息
fmt.Println("Mark become old")
mark.age = 46
fmt.Println("His age is", mark.age)
// 修改他的体重信息
fmt.Println("Mark is not an athlet anymore")
mark.weight += 60
fmt.Println("His weight is", mark.weight)
}

方法

Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:

func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
}

/* 定义函数 */
type Circle struct {
radius float64
}

func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("Area of Circle(c1) = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}

默认结构体是值传递,所以在方法中需要使用指针类型来给成员变量赋值

func (m *Member)setName(name string){/将Member改为*Member
m.Name = name
}

m := Member{}
m.setName("小明")
fmt.Println(m.Name)//小明

结构体打印字符串

fmt.Println( fmt.Sprintf("%v", head))

%+v 打印结构体时,会添加字段名
Printf("%+v", people) {Name:zhangsan}

内嵌

如果包含另一个结构体的时候不指定变量,可以直接访问。

package main

import (
"fmt"
)

type Phone struct {
price int
color string
}

type IPhone struct {
Phone
model string
}

func main() {
var p IPhone
p.price = 5000
p.color = "Black"
p.model = "iPhone 5"
fmt.Println("I have a iPhone:")
fmt.Println("Price:", p.price)
fmt.Println("Color:", p.color)
fmt.Println("Model:", p.model)
}

如果指定了变量,则需要通过变量访问

type IPhone struct {
phone Phone
model string
}

func main() {
var p IPhone
p.phone.price = 5000
p.phone.color = "Black"
p.model = "iPhone 5"
fmt.Println("I have a iPhone:")
fmt.Println("Price:", p.phone.price)
fmt.Println("Color:", p.phone.color)
fmt.Println("Model:", p.model)
}

使用 NEW 代替构造函数

项目目录结构
workspacepath -> oop -> employee -> employee.go

package employee

import (
"fmt"
)

type Employee struct {
FirstName string
LastName string
TotalLeaves int
LeavesTaken int
}

func (e Employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}

新建一个文件使用 employee
workspacepath -> oop -> employee -> employee.go
workspacepath -> oop -> main.go

package main

import "oop/employee"

func main() {
e := employee.Employee {
FirstName: "Sam",
LastName: "Adolf",
TotalLeaves: 30,
LeavesTaken: 20,
}
e.LeavesRemaining()

// 这里没有输入参数,没有实际意义。需要通过类似构造函数的机制来限制创建无参数对象
var e employee.Employee
e.LeavesRemaining()
}

修改上面的 employee 定义

package employee

import (
"fmt"
)

type employee struct {
firstName string
lastName string
totalLeaves int
leavesTaken int
}

func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {
e := employee {firstName, lastName, totalLeave, leavesTaken}
return e
}

func (e employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}

首先 employee 首字母改为小写,不导出给其他包使用。然后新建一个 New 导出函数用来创建对象

package main

import "oop/employee"

func main() {
e := employee.New("Sam", "Adolf", 30, 20)
e.LeavesRemaining()
}