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)
10、GO学习之 网络通信(Net/Http)
11、GO学习之 微框架(Gin)
12、GO学习之 数据库(mysql)
13、GO学习之 数据库(Redis)
14、GO学习之 搜索引擎(ElasticSearch)
15、GO学习之 消息队列(Kafka)
16、GO学习之 远程过程调用(RPC)
17、GO学习之 goroutine的调度原理
18、GO学习之 通道(nil Channel妙用)
19、GO学习之 同步操作sync包
20、GO学习之 互斥锁、读写锁该如何取舍
21、GO学习之 条件变量 sync.Cond
22、GO学习之 单例模式 sync.Once
23、GO 面试题总结一【面试官这样问】

前言

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

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

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

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

自定义类型是个新的类型,我们可以基于内置的类型来定义,也可以通过 struct 来定义自己的类型,就类似于 JAVA 中的自定义类。
下面的示例中,MyInt 就是一个自定义类型。

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() 方法。
当然结构体的声明还有别的方式,比如说结构体中可以用匿名字段,或者嵌套结构体,嵌套匿名结构体等,这个在项目实战中如果遇到,如果需要可以用一下。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值