基础概念
Go语言之中结构体的实例化,使用new或者&构造的类型实例为结构体指针
Go 语言不仅认为结构体能拥有方法,且每种自定义类型也可以拥有自己的方法
语法糖
.
来访问结构体指针的成员在Go语言中,对结构体进行&
取地址操作时,视为对该类型进行一次 new 的实例化操作
ins := &T{} //初始化内容为空
//T 表示结构体类型
//ins 为结构体的实例,类型为 *T,是指针类型
使用new的格式进行实例化
ins := new(T) //ins为T类型的指针
基本实例
var ins T
使用“键值对”初始化结构体,可以初始化一部分成员
//基本格式
ins := 结构体类型名{
字段1: 字段1的值,
字段2: 字段2的值,
…
}
//实例演示
type Cat struct {
name string
color string
}
func main() {
cat := Cat{name: "white"}
fmt.Println(cat) //{white }
}
使用多个值的列表初始化结构体:必须初始化结构体的所有字段、顺序一致、不能和键值对方式混用
//基本格式
ins := 结构体类型名{
字段1的值,
字段2的值,
…
}
//实例演示
type Cat struct {
name string
color string
}
func main() {
cat := Cat{"helloCat", "white"}
fmt.Println(cat) //{helloCat white}
}
初始化匿名结构体
匿名结构体的初始化写法由结构体定义和键值对初始化两部分组成
ins := struct {
// 匿名结构体字段定义
字段1 字段类型1
字段2 字段类型2
…
}{
// 字段值初始化
初始化字段1: 字段1的值,
初始化字段2: 字段2的值,
…
}
键值部分是可以选择的,不初始化成员时,格式可以变为
ins := struct {
字段1 字段类型1
字段2 字段类型2
…
}
使用演示
func printStruct(msg *struct {
name string
id int
}) {
fmt.Printf("%T\n", msg) //打印msg的类型 *struct { name string; id int }
}
func main() {
printStruct(&struct {
name string
id int
}{
"jim",
124,
})
}
Go语言的类型或结构体没有构造函数的功能,但是我们可以使用结构体初始化的过程来模拟实现构造函数
实际上就是定义一个函数,通过传入的参数进行结构体对象的构造
func NewCat(name, color string) Cat {
return Cat{name, color}
}
func main() {
cat := NewCat("hellocat", "white")
fmt.Println(cat) //{hellocat white}
}
接收器类型可以是(几乎)任何类型,不仅仅是结构体类型,任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型
类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在不同的源文件中,唯一的要求是它们必须是同一个包的
接收器的格式如下
func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {
函数体
}
指针类型的接收器由一个结构体的指针组成,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的
当方法作用于非指针接收器时,Go语言会在代码运行时将接收器的值复制一份,在非指针接收器的方法中可以获取接收器的成员值,但修改后无效
type Base struct {
name string
id int
}
//指针接收器
func (b *Base) ptrBase(name string, id int) {
b.id = id
b.name = name
}
//非指针接收器
func (b Base) objBase(name string, id int) {
b.id = id
b.name = name
}
func main() {
var b Base //声明对象
b.objBase("123", 123)
fmt.Println(b) //{ 0} -> 非指针接收器的修改不会被保存
b.ptrBase("456", 456)
fmt.Println(b) // {456 456} -> 指针接收器的修改被保存了
}
为基础类型添加方法
//重定义基础类型
type myint int
//实现方法
func (value myint)IsZero() bool {
return value==0
}
func main(){
var a myint =100
var b myint
fmt.Println(a.IsZero()) //false
fmt.Println(b.IsZero()) //true
}
示例:定义一个结构体方法和一个普通函数,使它们的参数完全一致,也就是方法和函数的签名一致
package main
import "fmt"
// 声明一个结构体
type class struct {
}
// 给结构体添加Do方法
func (c *class) Do(v int) {
fmt.Println("call method do:", v)
}
// 普通函数的Do
func funcDo(v int) {
fmt.Println("call function do:", v)
}
func main() {
// 声明一个函数回调
var delegate func(int)
// 创建结构体实例
c := new(class)
// 将回调设为c的Do方法
delegate = c.Do
// 调用
delegate(100) //call method do: 100
// 将回调设为普通函数
delegate = funcDo
// 调用
delegate(100) //call function do: 100
事件系统可以将事件派发者与事件处理者解耦
比如,网络底层可以生成各种事件,在网络连接之后,网络底层只需要将事件派发出去,而不需要关心到底哪些代码是用来响应连接上的逻辑。就如同微博,你关注了明星,明星的微博消息会通知你,但是他们并关注粉丝看到消息后的反应
事件系统基本原理图
事件的注册:事件系统需要为外部提供一个注册入口。这个注册入口传入注册的事件名称和对应事件名称的响应函数,事件注册的过程就是将事件名称和响应函数关联并保存起来;事件注册方通过事件系统注册应该响应哪些事件及如何使用回调函数处理这些事件
事件的调用:事件调用方和注册方是事件处理中完全不同的两个角色。事件调用方是事发现场,负责将事件和事件发生的参数通过事件系统派发出去,而不关心事件到底由谁处理
使用事件系统
package main
import "fmt"
// 实例化一个通过字符串映射函数切片的map
var eventByName = make(map[string][]func(interface{}))
// 注册事件,提供事件名和回调函数
func RegisterEvent(name string, callback func(interface{})) {
// 通过名字查找事件列表 -> 通过名字得到函数列表切片。第一次注册时,得到的是空的列表切片
list := eventByName[name]
// 在列表切片中添加函数 -> 将传入的函数添加到对应的名字切片之中
list = append(list, callback)
// 将修改的事件列表切片保存回去
eventByName[name] = list
}
// 调用事件
func CallEvent(name string, param interface{}) {
// 通过名字找到事件列表
list := eventByName[name]
// 遍历这个事件的所有回调
for _, callback := range list {
// 传入参数调用回调
callback(param)
}
}
func main() {
//注册事件
RegisterEvent("test",func(event interface{}) {fmt.Println(event)})
RegisterEvent("test",func(event interface{}) {fmt.Println("second register:",event)})
//调用事件
CallEvent("test","i am here!!")
//运行结果
// i am here!!
// second register: i am here!!
}
结构体可以包含一个或者多个匿名(内嵌)字段,即这些字段没有显示的名字,只有字段的类型是必须的,因此在结构体之中,对于每一种数据类型只能有一个匿名字段
匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌结构体
可以粗略地将这个和面向对象语言中的继承概念相比较,随后将会看到它被用来模拟类似继承的行为。Go语言中的继承是通过内嵌或组合来实现的,所以可以说,在Go语言中,相比较于继承,组合更受青睐
package main
import "fmt"
type Base struct {
value1 int
value2 int
}
type Child struct {
v1 int
v2 int
//匿名字段
int
Base
}
func main() {
child := new (Child)
child.v1=100
child.v2=200
child.int=300 //用类型代替名称
child.value1=400
child.value2=500
fmt.Println(child) //&{100 200 300 {400 500}}
//换一种赋值的方式
child2 := &Child{1000,2000,3000,Base{4000,5000}}
fmt.Println(child2) //&{1000 2000 3000 {4000 5000}}
}
package main
import (
"encoding/json"
"fmt"
)
//1、定义数据结构
// 定义手机屏幕
type Screen struct {
Size float32 // 屏幕尺寸
ResX, ResY int // 屏幕水平和垂直分辨率
}
// 定义电池
type Battery struct {
Capacity int // 容量
}
//准备Json数据,准备手机数据结构,填充数据,将数据序列化为 JSON 格式的字节数组
// 生成json数据
func genJsonData() []byte {
// 完整数据结构
raw := &struct {
Screen //手机屏幕
Battery //电池
HasTouchID bool // 序列化时添加的字段:是否有指纹识别
}{
// 屏幕参数
Screen: Screen{
Size: 5.5,
ResX: 1920,
ResY: 1080,
},
// 电池参数
Battery: Battery{
2910,
},
// 是否有指纹识别
HasTouchID: true,
}
// 将数据序列化为json
jsonData, _ := json.Marshal(raw)//raw之中包含了参数结构体,这样序列化一次即可
return jsonData //返回json数据
}
//分离JSON数据
func main() {
// 生成一段json数据
jsonData := genJsonData() //拿到json数据
fmt.Println(string(jsonData))//打印json数据 {"Size":5.5,"ResX":1920,"ResY":1080,"Capacity":2910,"HasTouchID":true}
// 只需要屏幕和指纹识别信息的结构和实例
screenAndTouch := struct {
Screen
HasTouchID bool
}{}
// 反序列化到screenAndTouch
json.Unmarshal(jsonData, &screenAndTouch)
// 输出screenAndTouch的详细结构
fmt.Printf("%+v\n", screenAndTouch) //打印结构体数据 {Screen:{Size:5.5 ResX:1920 ResY:1080} HasTouchID:true}
// 只需要电池和指纹识别信息的结构和实例
batteryAndTouch := struct {
Battery
HasTouchID bool
}{}
// 反序列化到batteryAndTouch
json.Unmarshal(jsonData, &batteryAndTouch)
// 输出screenAndTouch的详细结构
fmt.Printf("%+v\n", batteryAndTouch) //打印结构体数据{Battery:{Capacity:2910} HasTouchID:true}
}
finalizer(终止器)是与对象关联的一个函数,通过 runtime.SetFinalizer 来设置,如果某个对象定义了 finalizer,当它被 GC 时候,这个 finalizer 就会被调用,以完成一些特定的任务,例如发信号或者写日志等
Go语言中 SetFinalizer 函数定义如下
func SetFinalizer(x, f interface{})
参数说明
执行流程
SetFinalizer(x, nil)
来清理绑定到 x 上的终止器示例
package main
import (
"log"
"runtime"
"time"
)
type Road int
func findRoad(r *Road) {
log.Println("road:", *r)
}
func entry() {
var rd Road = Road(999)
r := &rd
//设置终止其,参数为int*,终止其为打印参数的地址
runtime.SetFinalizer(r, findRoad)
}
func main() {
entry() //进行调用
for i := 0; i < 10; i++ {
time.Sleep(time.Second)//进行休眠
runtime.GC()//手动GC
}
}
// 2022/02/09 22:47:00 road: 999 十次循环只打印一次,说明当x被GC时,才会调用
普通示例
package main
import (
"encoding/json"
"fmt"
)
type base struct {
Name string
Age int
}
func main() {
b := base{
Name: "Jim",
Age: 50,
}
fmt.Println(b) //{Jim 50}
jsonData, err := json.Marshal(b) //转换为json格式
if err == nil {
stringData := string(jsonData) //转换成字符串
fmt.Println(stringData) //{"Name":"Jim","Age":50}
}
}
添加标签
上述得到的json数据为驼峰形式,添加标签
type base struct {
Name string `json:"name"`
Age int `json:"age"`
}
{Jim 50}
{"name":"Jim","age":50} //小写形式
添加 omitempty标签忽略空值
package main
import (
"encoding/json"
"fmt"
)
type base struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
b := base{
Name: "Jim",
}
fmt.Println(b) //{Jim 0}
jsonData, err := json.Marshal(b) //转换为json格式
if err == nil {
stringData := string(jsonData) //转换成字符串
fmt.Println(stringData) //{"name":"Jim","age":0}
}
}
//添加后
type base struct {
Name string `json:"name"`
Age int `json:"age,omitempty"`
}
{Jim 0}
{"name":"Jim"} //可以看到没有输出age字段
单向链表的三个概念
头节点不是必须的,头结点的好处
使用struct定义单链表
package main
import "fmt"
type list struct {
data int
next *list
}
func Print(p *list) {
for p != nil {
fmt.Println(*p)
p = p.next
}
}
func main() {
head := &list{1, nil}
node1 := &list{2, nil}
node2 := &list{3, nil}
head.next = node1
node1.next = node2
Print(head)
}
//运行结果
{1 0xc00010c210}
{2 0xc00010c220}
{3 <nil>}
插入节点
头插法
func main() {
head := &list{1, nil}
node1 := &list{2, nil}
node2 := &list{3, nil}
head.next = node1
node1.next = node2
node3 := &list{0, nil}
node3.next = head
head = node3
Print(head)
}
//运行结果
{0 0xc000096210}
{1 0xc000096220}
{2 0xc000096230}
{3 <nil>}
尾插法
package main
import "fmt"
type list struct {
data int
next *list
}
func Print(p *list) {
for p != nil {
fmt.Println(*p)
p = p.next
}
}
func Pushback(head,node *list) {
for head.next!= nil {
head=head.next
}
head.next=node
}
func main() {
head := &list{1, nil}
node1 := &list{2, nil}
node2 := &list{3, nil}
head.next = node1
node1.next = node2
node3 := &list{4, nil}
Pushback(head,node3)
Print(head)
}
//运行结果
{1 0xc000010230}
{2 0xc000010240}
{3 0xc000010250}
{4 <nil>}