GO学习之 结构体 操作

GO系列

1、GO学习之Hello World
2、GO学习之入门语法
3、GO学习之切片操作
4、GO学习之 Map 操作
5、GO学习之 结构体 操作
6、GO学习之 通道(Channel)
7、GO学习之 多线程(goroutine)
8、GO学习之 函数(Function)
9、GO学习之 接口(Interface)

前言

按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
对于切片(slice)和 Map,开发中经常用到这两种类型,逃不过的,当然针对于一些简单的 API 开发来说,但是在大部分情况下还需要自己定义数据结构,开发语言自身提供的这种还是无法满足复杂的业务场景的,此篇博客就来了解 go 语言中是如何进行自定义类型的,就像 JAVA 中可以定一个实体 Bean (class Persion)来封装数据。

Go 语言中没有 “类” 的概念,也就不能像 JAVA 中那样自己定义类来封装数据,Go 语言中通过 结构体 的内嵌来实现自定义数据结构,这样比面向对象更具有扩展性和灵活性。

一、自定义类型和类型别名

在 Go 语言中有自带的数据类型,如 string、整型、浮动型、布尔等基本的数据类型, Go 语言可以使用 type 关键字来定义自定义类型。

自定义类型是个新的类型,我们可以基于内置的类型来定义,也可以通过 struct 来定义。

package main

import "fmt"

func main() {
    
    
	type myInt int
	var aa myInt
	aa = 100
	fmt.Println(aa)
}

类型别名是 Go1.9 版本添加的新功能,类型别名知识类型的别名,本质上是同一个类型。

package main

import "fmt"

// 类型定义
type myInt int
// 类型别名
type myInt2 = int

func main() {
    
    

	var a myInt
	var b myInt2

	fmt.Printf("type of a:%T\n", a)
	fmt.Printf("type of b:%T\n", b)
}

运行结果:

PS D:\workspaceGo> go run customType.go
type of a:main.myInt
type of b:int

从运行结果可以看出,a 的类型是 main.MyInt,而 b 的类型是 int,所以类型别名 MyInt2 只会存在于代码中,编译完成并不会存在。

二、结构体

  • Go 语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体(struct)。
  • 使用 type 和 struct 关键字来定义结构体;
  • 类型名:自定义结构体的名称,同一个包内不能重复,就像JAVA 的类名,同一个包下不能类名重复;
  • 字段名:每个结构体的字段名必须唯一,就像JAVA的一个实体,字段不能重复;
  • 字段类型:结构体字段的具体类型,可以是基本类型,函数类型等;

2.1 基本实例化

package main

import "fmt"

func main() {
    
    
	// 声明一个 person 变量
	var p person
	p.id = 1
	p.name = "张三"
	p.age = 28
	p.gender = 1
	fmt.Printf("p的值为:%+v\n", p)
	fmt.Printf("p的值为:%#v\n", p)
	// 通过 new 来实例化,指针类型结构体
	var p2 = new(person)
	p2.id = 2
	p2.name = "王老五"
	p2.age = 48
	p2.gender = 0
	fmt.Printf("p2的值为:%T\n", p2)
	fmt.Printf("p2的值为:%#v\n", p2)
}

// 定义 person 的自定义类型
type person struct {
    
    
	id          int64
	name        string
	age, gender int8
}

运行结果:

PS D:\workspaceGo> go run customType.go
p的值为:{
    
    id:1 name:张三 age:28 gender:1}
p的值为:main.person{
    
    id:1, name:"张三", age:28, gender:1}
p2的值为:*main.person
p2的值为:&main.person{
    
    id:2, name:"王老五", age:48, gender:0}

通过运行结果可以看出,我们成功的 定义了一个 person 类型,声明一个变量 p 并且赋值给每个 person 的字段,也可以通过 new 关键词来实例化,这样得到的是 p2 指针,我们照样可以通过指针来访问结构体成员。

2.2 匿名实例化

在需要一些临时数据结构的场景中,自个自定义类型只需要使用一次或者不需要在外部访问,这时候可以使用匿名结构体更加方便。

package main

import "fmt"

func main() {
    
    
	//定义一个 user 结构体
	var user struct {
    
    
		id   int64
		name string
		age  int8
	}
	user.id = 1
	user.name = "李四"
	user.age = 30
	fmt.Printf("user的值为:%+v\n", user)
	fmt.Printf("user的值为:%#v\n", user)
}

运行结果:

PS D:\workspaceGo> go run customType.go
user的值为:{
    
    id:1 name:李四 age:30}
user的值为:struct {
    
     id int64; name string; age int8 }{
    
    id:1, name:"李四", age:30}

从运行结果看出,自定义结构体赋值给 user 变量,但是这个结构体没有名字。

2.3 通过结构体地址实例化

package main

import "fmt"

func main() {
    
    
	// 获取到 person 的实例地址
	p := &person{
    
    }
	fmt.Printf("%T\n", p)
	fmt.Printf("%#v\n", p)

	p.id = 3
	p.name = "赵四"
	p.gender = 2
	p.age = 56
	fmt.Printf("%#v\n", p)
}

// 定义 person 的自定义类型
type person struct {
    
    
	id          int64
	name        string
	age, gender int8
}

运行结果:

PS D:\workspaceGo> go run customType.go
*main.person
&main.person{
    
    id:0, name:"", age:0, gender:0}
&main.person{
    
    id:3, name:"赵四", age:56, gender:2}

p.name = “赵四” 其实是 (*p).name = “赵四”。

三、结构体初始化

3.1 使用键值对初始化

package main
import "fmt"

func main() {
    
    
	p := person{
    
    
		id:     1,
		name:   "翠花",
		gender: 0,
		age:    25,
	}
	fmt.Printf("初始化的 p 值为:%+v\n", p)
	// 也可以对结构体指针进行键值初始化
	p2 := &person{
    
    
		id:     2,
		name:   "燕子",
		gender: 0,
	}
	fmt.Printf("通过结构体指针初始化 p2 值为:%+v\n", p2)

}
// 定义 person 的自定义类型
type person struct {
    
    
	id          int64
	name        string
	age, gender int8
}

运行结果:

PS D:\workspaceGo> go run customType.go
初始化的 p 值为:{
    
    id:1 name:翠花 age:25 gender:0}
通过结构体指针初始化 p2 值为:&{
    
    id:2 name:燕子 age:0 gender:0}

3.2 值列表初始化

初始化结构体的时候可以简写,就是不写键,直接写值,使用值的列表方式初始化需要注意以下几点:

  • 必须初始化结构体的所有字段
  • 初始化值的填充顺序与字段在结构体中的声明顺序要一致
  • 值列表初始化不能和键值对初始化混用
package main

import "fmt"

func main() {
    
    
	p := &person{
    
    
		1,
		"翠花",
		0,
		25,
	}
	fmt.Printf("初始化的 p 值为:%+v\n", p)
}
// 定义 person 的自定义类型
type person struct {
    
    
	id          int64
	name        string
	age, gender int8
}

运行结果:

PS D:\workspaceGo> go run customType.go
初始化的 p 值为:&{
    
    id:1 name:翠花 age:0 gender:25}

3.3 匿名结构体初始化

匿名结构体初始化格式:变量 := struct { 成员名 类型 }{ 成员 初始化值 }

package main

import "fmt"

func main() {
    
    
	user := struct {
    
    
		id   int
		name string
	}{
    
    
		id:   1,
		name: "小可",
	}
	fmt.Printf("匿名用户 user id: %+v, name: %v\n", user.id, user.name)
}

运行结果:

PS D:\workspaceGo\src\structTest> go run .\anoStruct.go
匿名用户 user id: 1, name: 小可

四、方法和参数接受者

方法的定义格式如下:func(参数名 参数类型) 方法名 (参数列表) (放回参数) { }

  • 接受的参数名,官方建议类型名第一个字母
  • 参数类型,可以是指针类型 和 非指针类型
  • 方法名、参数列表、放回参数 格式与函数定义相同

4.1 结构体类型接受参数

package main

import "fmt"

func main() {
    
    
	p1 := NewPerson(1, "phen")
	p1.drame()
}
// 返回指针类型 person
func NewPerson(id int64, name string) *person {
    
    
	return &person{
    
    
		id:   id,
		name: name,
	}
}
// person 的方法
func (p person) drame() {
    
    
	fmt.Printf("%s 的梦想是变有钱!!!", p.name)
}
type person struct {
    
    
	id   int64
	name string
}

运行结果:

PS D:\workspaceGo> go run customType.go
phen 的梦想是变有钱!!!

方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。

4.2 指针类型接受参数

方法的定义格式如下:
func(参数名 *参数类型) 方法名(参数列表)(放回参数){
函数体
}

package main

import "fmt"

func main() {
    
    
	p1 := NewPerson(1, "phen")
	p1.setAge(26)
	fmt.Printf("%s的年纪是 %d\n", p1.name, p1.age)
}

// 返回指针类型 person
func NewPerson(id int64, name string) *person {
    
    
	return &person{
    
    
		id:   id,
		name: name,
	}
}

// 使用指针接受参数,设置 age 字段的值
func (p *person) setAge(age int8) {
    
    
	p.age = age
}

type person struct {
    
    
	id   int64
	name string
	age  int8
}

运行结果:

PS D:\workspaceGo> go run customType.go
phen的年纪是 26

4.3 值类型接受参数

当方法使用值类型接受参数时,Go 语言会在代码运行时将接受者的值复制一份,修改操作只是针对副本,无法修改接受者变量本身。

package main

import "fmt"

func main() {
    
    
	p1 := NewPerson(1, "phen", 18)
	fmt.Printf("修改前%s的年纪是 %d\n", p1.name, p1.age)
	p1.setAge(26)
	fmt.Printf("修改后%s的年纪是 %d\n", p1.name, p1.age)
}

// 返回指针类型 person
func NewPerson(id int64, name string, age int8) *person {
    
    
	return &person{
    
    
		id:   id,
		name: name,
		age:  age,
	}
}

// 使用指针接受参数,设置 age 字段的值
func (p person) setAge(age int8) {
    
    
	p.age = age
	fmt.Printf("这里%s的年纪是 %d\n", p.name, p.age)
}

type person struct {
    
    
	id   int64
	name string
	age  int8
}

运行结果:

PS D:\workspaceGo> go run customType.go
修改前phen的年纪是 18
这里phen的年纪是 26
修改后phen的年纪是 18

五、结构体与JSON序列化

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成,通常是用的最多的一种数据传递方式,接口中返回 JSON 格式,数据转换用 JSON 格式,那 Go 中必然也可以结构体与 JSON 相互转换。

package main

import (
	"encoding/json"
	"fmt"
)

// 定义学生
type Student struct {
    
    
	Id    int
	Name  string
	Score float32
}

// 定义班级
type Class struct {
    
    
	Title    string
	Students []*Student
}

func main() {
    
    
	// 初始化数据
	c := &Class{
    
    
		Title:    "帅哥靓女一班",
		Students: make([]*Student, 0, 50),
	}
	for i := 1; i < 10; i++ {
    
    
		stu := &Student{
    
    
			Id:    i,
			Name:  fmt.Sprintf("stu%02d", i),
			Score: 100.0,
		}
		c.Students = append(c.Students, stu)
	}
	// JSON 序列化
	data, err := json.Marshal(c)
	if err != nil {
    
    
		fmt.Println("json marshal failed")
		return
	}
	fmt.Printf("转换的JSON为:%s\n", data)
	// JSON 反序列化
	str := `{"Title":"帅哥靓女二班","Students":[{"Id":1,"Name":"王帅","Score":100},{"Id":9,"Name":"陈美丽","Score":100}]}`
	c1 := &Class{
    
    }
	err = json.Unmarshal([]byte(str), c1)
	if err != nil {
    
    
		fmt.Println("json unmarshal failed")
		return
	}
	fmt.Printf("反序列化的班级为:%+v\n", c1)
}

运行结果:

转换的JSON为:{
    
    "Title":"帅哥靓女一班","Students":[{
    
    "Id":1,"Name":"stu01","Score":100},{
    
    "Id":2,"Name":"stu02","Score":100},{
    
    "Id":3,"Name":"stu03","Score":100},{
    
    "Id":4,"Name":"stu04","Score":100},{
    
    "Id":5,"Name":"stu05","Score":100},{
    
    "Id":6,"Name":"stu06","Score":100},{
    
    "Id":7,"Name":"stu07","Score":100},{
    
    "Id":8,"Name":"stu08","Score":100},{
    
    "Id":9,"Name":"stu09","Score":100}]}
反序列化的班级为:&{
    
    Title:帅哥靓女二班 Students:[0xc000054560 0xc0000545a0]}

六、总结

在Go 语言中,接受者的类型可以是任一类型,不仅仅是结构体,任何类型都可以拥有方法,上面的例子中是以结构体 person 来添加方法,我们可以通过 p.drame() 调用,就类似 JAVA 中的一个 public void drame(){} 方法,可以在其他类中调用本类的 drame() 方法。
当然结构体的声明还有别的方式,比如说结构体中可以用匿名字段,或者嵌套结构体,嵌套匿名结构体等,这个在项目实战中如果遇到,如果需要可以用一下。

猜你喜欢

转载自blog.csdn.net/qq_19283249/article/details/131865994