《Go语言从入门到进阶实战》学习笔记:第六章 结构体

6.1定义结构体

定义格式:

type 类型名 struct{
字段1 字段1类型
字段2 字段2类型
...
}

6.2实例化结构体--为结构体分配内存并初始化

1、基本的实例化形式

var ins T

T为结构体类型,ins为结构体的实例。

2、创建指针类型的结构体

ins:=new(T)

3、取结构体的地址实例化(应用最广泛)

ins:=&T{}

6.3初始化结构体的成员变量

初始化有两种形式:一种是字段“键值对”形式,一种是多个值的列表形式。

1、使用“键值对”初始化结构体

格式

ins:=结构体类型名{
    字段1:字段1的值,
    字段2:字段2的值,
...
}

例子:

type People struct{
    name string
    chile *People
}

relation:=&People{
    name:"爷爷",
    child:&People{
        name:"爸爸",
        child:&People{
            name:"我",
        },
    },
}

2、使用多个值的列表初始化结构体

格式

ins:=结构体类型名{
    字段1的值,
    字段2的值,
...
}

注意:

这种方式必须初始化结构体的所有字段,每一个初始值的填充顺序必须与字段在结构体中的声明顺序一致,键值对与值列表的初始化形式不能混用。

例子:

type Address struct{
    Province string
    City    string
    Zip_Code int
    Phone_Number string
}

addr:=Address{
    "四川",
    "成都",
    610000,
    "0",
}

fmt.Println(addr)

3、初始化匿名结构体

(1)匿名结构体定义格式和初始化写法

ins:=struct{
    //匿名结构体字段定义
    字段1 字段类型1
    字段2 字段类型2
...
}{
    //字段值初始化
    初始化字段1:字段1的值,
    初始化字段2:字段2的值,
...
}

//键值对初始化部分是可选的,不初始化成员时,匿名结构体格式变为
ins:=struct{
    //匿名结构体字段定义
    字段1 字段类型1
    字段2 字段类型2
...
}{}

(2)使用匿名结构体的例子

//打印消息类型,传入匿名结构体
func print_Msg_Type(msg *struct{
	id int
	data string
})  {
	//使用动词%T打印msg的类型
	fmt.Printf("%T\n",msg)
}

func main()  {
	
	//实例化一个匿名结构体
	msg:=&struct {
		//定义部分
		id int
		data string
	}{
		//值初始化部分
		1024,
		"hello",
	}
	print_Msg_Type(msg)
}

6.4构造函数--结构体和类型的一系列初始化操作的函数封装

Go语言的类型或结构体没有构造函数的功能。结构体的初始化过程可以使用函数封装实现。

1、多种方式创建和初始化结构体--模拟构造函数重载

type Cat struct{
    Color sting
    Name string
}

func New_Cat_By_Name(name string) *Cat{

    return &Cat{
        Name:name,
    }
}

func New_Cat_By_Color(color string) *Cat{
    return &Cat{
        Color:color,
    }
}

Go语言中没有函数重载

2、带有父子关系的结构体的构造和初始化--模拟父级构造调用

type Cat struct{
    Color string
    Name string
}

type Black_Cat struct{
    Cat//嵌入Cat类似于派生
}

//“构造基类”
func New_Cat(name string) *Cat{
    return &Cat{
        Name:name,
    }

}

//构造子类
func New_Black_Cat(color string)*Black_Cat{
    cat:=&Black_Cat{}//实例化Black_Cat结构,此时Cat也同时被实例化
    cat.Color=color
    return cat
}

6.5方法

Go语言中的方法是一种作用于特定类型变量的函数。这种特定类型变量叫做接收器。

1、为结构体添加方法

(1)面向过程实现方法

package main

type Bag struct {
	items []int
}

//将物品放入背包的过程
func Insert(b *Bag,itemid int)  {
	b.items=append(b.items,itemid)
}

func main()  {
	//bag:=new(Bag)
	bag:=&Bag{}
	Insert(bag,1001)
}

(2)Go语言的结构体方法

package main

type Bag struct {
	items []int
}

//将物品放入背包,(b *Bag)表示接收器即Insert作用的对象的实例,
// 相当于面向对象语言的this,self,每个方法只能有一个接收器
func (b *Bag)Insert(itemid int)  {
	b.items=append(b.items,itemid)
}

func main()  {
	b:=new(Bag)
	b.Insert(1001)
}

2、接收器--方法作用的目标

接收器格式如下:

func(接收器变量 接收器类型)方法名(参数列表)(返回参数){
函数体
}

接收器分为指针类型接收器和非指针类型接收器

(1)理解指针类型的接收器

package main

import "fmt"

//定义属性结构
type Property struct {
	value int//属性值
}
//设置属性值
func (p *Property)set_Value(v int)  {
	//修改p的成员变量
	p.value=v
}

//取属性值
func (p *Property)get_Value() int {
	return p.value
}

func main()  {
	//实例化属性
	p:=new(Property)
	//设置值
	p.set_Value(100)
	//打印值
	fmt.Println(p.get_Value())
}

(2)理解非指针类型的接收器

//非指针接收器不能对成员进行修改
package main

import "fmt"

type Point struct {
	X int
	Y int
}
//非指针接收器的加方法
func (p Point)Add(other Point)Point  {
	//成员值与参数相加后返回新的结构
	return Point{p.X+other.X,p.Y+other.Y}
}

func main()  {
	//初始化点
	p1:=Point{1,1}
	p2:=Point{2,2}
	//与另外一个点相加
	result:=p1.Add(p2)
	//输出结果
	fmt.Println(result)
}

(3)指针和非指针接收器的使用

小对象适合非指针接收器,大对象适合使用指针接收器,在接收器和参数见传递时不进行复制,只是传递指针。

3、示例:二维矢量模拟玩家移动

(1)实现二维矢量结构

//实现二维矢量结构
package main

import "math"

type Vec2 struct {
	X,Y float32
}

//使用矢量加上另外一个矢量,生成新的矢量
func (v Vec2)Add(other Vec2) Vec2 {
	return Vec2{v.X+other.X,v.Y+other.Y}
}

//使用矢量减去另一个矢量
func (v Vec2)Sub(other Vec2) Vec2 {
	return Vec2{v.X-other.x,v.Y-other.Y}
}
//使用一个矢量乘以另一个矢量
func (v Vec2)Scale(s float32) Vec2 {
	return Vec2{v.X*s,v.Y*s}
}
//计算两个矢量之间的距离
func (v Vec2)Distance_To(other Vec2)float32  {
	dx:=v.X-other.X
	dy:=v.Y-other.Y
	return float32(math.Sqrt(float64(dx*dx+dy*dy)))
}

//返回当前矢量的标准化矢量
func (v Vec2)Normalize()Vec2  {
	mag:=v.X*v.x+v.y*v.Y
	if mag>0{
		one_Over_Mag:=1/float32(math.Sqrt(float64(mag)))
		return Vec2{v.x*one_Over_Mag,v.Y*one_Over_Mag}
	}
	return Vec2{0,0}
}



(2)实现玩家对象

package main

type Player struct {
	curr_Pos Vec2//当前位置
	target_Pos Vec2//目标位置
	speed float32//移动速度
}

//设置玩家移动的目标位置
func (p *Player)Move_To(v Vec2)  {
	p.target_Pos=v
}
//获取当前位置
func (p *Player)Pos()Vec2  {
	return p.curr_Pos
}

//判断是否到达目的地
func (p *Player)Is_Arrived()bool  {
	//通过计算当前玩家位置与目标位置的距离不超过移动的步长,判断已经到达目标点
	return p.curr_Pos.Distance_To(p.target_Pos)<p.speed
}

//更新玩家位置
func (p *Player)Update()  {
	if !p.Is_Arrived(){
		//计算出当前位置指向目标的朝向
		dir:=p.target_Pos.Sub(p.curr_Pos).Normalize()
		//添加速度矢量生成新的位置
		new_Pos:=p.curr_Pos.Add(dir.Scale(p.speed))
		//移动完成后,更新当前位置
		p.curr_Pos=new_Pos
	}
}

//创建新玩家
func New_Player(speed float32)*Player  {
	return &Player{speed:speed,}
}

(3)处理移动逻辑

//实现二维矢量结构
package main

import "math"

type Vec2 struct {
	X,Y float32
}

//使用矢量加上另外一个矢量,生成新的矢量
func (v Vec2)Add(other Vec2) Vec2 {
	return Vec2{v.X+other.X,v.Y+other.Y}
}

//使用矢量减去另一个矢量
func (v Vec2)Sub(other Vec2) Vec2 {
	return Vec2{v.X-other.X,v.Y-other.Y}
}
//使用一个矢量乘以另一个矢量
func (v Vec2)Scale(s float32) Vec2 {
	return Vec2{v.X*s,v.Y*s}
}
//计算两个矢量之间的距离
func (v Vec2)Distance_To(other Vec2)float32  {
	dx:=v.X-other.X
	dy:=v.Y-other.Y
	return float32(math.Sqrt(float64(dx*dx+dy*dy)))
}

//返回当前矢量的标准化矢量
func (v Vec2)Normalize()Vec2  {
	mag:=v.X*v.X+v.Y*v.Y
	if mag>0{
		one_Over_Mag:=1/float32(math.Sqrt(float64(mag)))
		return Vec2{v.X*one_Over_Mag,v.Y*one_Over_Mag}
	}
	return Vec2{0,0}
}



这里遇到了跨文件调用的问题。解决方法:https://blog.csdn.net/qq_36214481/article/details/88791692

4、为类型添加方法

(1)为基本类型添加方法

package main

import "fmt"

//将int定义为My_Int
type My_Int int

//为My_Int添加Is_Zero()方法
func (m My_Int)Is_Zero()bool  {
	return m==0
}

//为My_Int添加Add()方法
func (m My_Int)Add(other int) int {
	return int(m)+other
}

func main()  {
	var b My_Int
	fmt.Println(b.Is_Zero())
	b=1
	fmt.Println(b.Add(2))
}

(2)http包中的类型方法

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

func main()  {
	client:=&http.Client{}
	//创建一个http请求
	req,err:=http.NewRequest("POST","http://www.sougo.com/",strings.NewReader("key=value"))

	//发现错误就打印退出
	if err!=nil{
		fmt.Println(err)
		os.Exit(1)
		return
	}

	//为标头添加信息
	req.Header.Add("User-Agent","my Client")
	//开始请求
	resp,err:=client.Do(req)
	//处理请求错误
	if err!=nil{
		fmt.Println(err)
		os.Exit(1)
		return
	}

	//读取服务器返回的内容
	data,err:=ioutil.ReadAll(resp.Body)
	fmt.Println(string(data))
	defer  resp.Body.Close()
}

5、示例:使用事件系统实现事件的响应和处理

(1)方法和函数的统一调用

结构体方法和普通函数的参数完全一致,也就是方法和函数的签名一致。

package main

import "fmt"

//声明一个结构体
type class struct {

}

//给结构体添加Do()方法
func (c *class)Do(v int)  {
	fmt.Println("call method do:",v)
}

//普通函数的Do()方法
func func_Do(v int)  {
	fmt.Println("call method do:",v)
}

func main()  {
	//声明一个函数回调
	var delegate func(int)
	//创建结构体实例
	c:=new(class)
	//将回调设为c的Do方法
	delegate=c.Do
	//调用
	delegate(100)
	//将回调函数设为普通函数
	delegate=func_Do
	//调用
	delegate(100)
}

(2)事件系统基本原理

事件系统的特点

i 能够实现事件的一方,可以根据事件ID或名字注册对应的事件。

ii 事件发起者,会根据注册信息通知这些注册者。

iii 一个事件可以有多个实现方响应

(3)注册事件

事件注册的过程就是将事件名称和响应函数关联并保存起来。

package main

//实例化一个通过字符串映射函数切片的map
var event_By_Name=make(map[string][]func(interface{}))

//注册事件,提供事件名和回调函数
func Register_Event(name string,callback func(interface{}))  {
	//通过名字查找事件列表
	list:=event_By_Name[name]
	//在列表切片中添加函数
	list=append(list,callback)
	//保存修改的事件列表切片
	event_By_Name[name]=list
}

(4)事件调用

//调用事件
func call_Event(name string,param interface{})  {
	//通过名字查找事件列表
	list:=event_By_Name[name]
	//遍历这个事件的所有回调
	for _,callback:=range list{
		//传入参数调用回调
		callback(param)
	}
}

(5)使用事件系统

package main

import "fmt"

type Actor struct {

}

//为角色添加一个事件处理函数
func (a *Actor)On_Event(param interface{})  {
	fmt.Println("actor event:",param)
}
//全局事件
func Global_Event(param interface{})  {
	fmt.Println("global event:",param)
}

func main()  {
	//实例化一个角色
	a:=new(Actor)

	//注册名为On_Skill的回调
	Register_Event("On_Skill",a.On_Event)
	//再次在On_Skill上注册全局事件
	Register_Event("On_Skill",Global_Event)
	//调用事件,所有注册的同名函数都会被调用
	call_Event("On_Skill",100)
}

6.6类型内嵌和结构体内嵌

结构体允许其成员字段在声明时没有字段名而只有类型,这种形式的字段被称为类型内嵌或匿名字段。

type Data struct{
    int
    float32
    bool
}

ins:=&Data{
    int: 10,
    float32: 3.14,
    bool: true,
}

类型内嵌其实仍然拥有自己的字段名,只是字段名就是其类名本身,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

结构体实例化后,如果匿名的字段类型为结构体,那么可以直接访问匿名结构体里的所有成员,这种方法被称为结构体内嵌。

1、声明结构体内嵌

非内嵌写法的例子

package main

import "fmt"

//基础颜色
type Basic_Color struct {
	R,G,B float32
}

//完整颜色
type Color struct {
	Basic Basic_Color
	//透明度
	Alpha float32
}

func main()  {
	var c Color
	//设置基本颜色分量
	c.Basic.R=1
	c.Basic.G=1
	c.Basic.B=0
	//设置透明度
	c.Alpha=1
	//显示整个结构体内容
	fmt.Printf("%+v",c)
}

内嵌写法

package main

import "fmt"

type Basic_Color struct {
	R,G,B float32
}

type Color struct {
	Basic_Color
	Alpha float32
}

func main()  {
	var c Color
	c.R=1
	c.G=1
	c.B=0

	c.Alpha=1
	fmt.Printf("%+v",c)
}

2、结构体内嵌特性

(1)内嵌的结构体可以直接访问其成员变量

(2)内嵌结构体的字段名是它的类型名

3、使用组合思想描述对象特性

Go语言中的结构体内嵌特性就是一种组合特性,使用组合特性可以快速构建对象的不同特性。

例子:人和鸟的特征

package main

import "fmt"

//可飞行的
type Flying struct {

}

func (f *Flying)Fly()  {
	fmt.Println("can fly")
}

//可行走的
type Walkable struct {

}

func (w *Walkable)Walk()  {
	fmt.Println("can walk")
}

//人类
type Human struct {
	Walkable//人类能行走
}

//鸟类
type Bird struct {
	Walkable//鸟类能行走
	Flying//鸟类能飞行
}

func main()  {
	//实例化鸟类
	b:=new(Bird)
	fmt.Println("Bird:")
	b.Fly()
	b.Walk()
	//实例化人类
	h:=new(Human)
	fmt.Println("Human:")
	h.Walk()
}

4、初始化结构体内嵌

结构体内嵌初始化时,将结构体内嵌的类型作为字段名像普通结构体一样进行初始化。

package main

import "fmt"

//车轮
type Wheel struct {
	Size int
}

//引擎
type Engine struct {
	Power int//功率
	Type string //类型
}
//车
type Car struct {
	Wheel
	Engine
}

func main()  {
	c:=Car{
		//初始化轮子
		Wheel:Wheel{
			Size:18,
		},
		Engine:Engine{
			Type:"1.4T",
			Power:143,
		},
	}
	fmt.Printf("%+v\n",c)
}

5、初始化内嵌匿名结构体

package main

import "fmt"

type Wheel struct {
	Size int
}

type Car struct {
	Wheel
	Engine struct{
		Power int
		Type string
	}
}

func main()  {
	c:=Car{
		Wheel:Wheel{
			Size:18,
		},
		Engine:struct{
			Power int
			Type string
		}{
			Type:"1.4T",
			Power:143,
		},
	}
	fmt.Printf("%+v\n",c)
}

6.7示例:使用匿名结构体分离JSON数据

package main

import (
	"encoding/json"
	"fmt"
)

//定义手机屏幕
type Screen struct {
	Size float32//屏幕大小
	Res_X,Res_Y int//屏幕水平和垂直分辨率
}

//定义电池
type Battery struct {
	Capacity int//电池容量

}

//生成JSON数据
func gen_Json_Data()[]byte  {
	//完整数据结构
	raw:=&struct {
		Screen
		Battery
		Has_Touch_ID bool//序列化时添加的字段:是否有指纹识别
	}{
		//屏幕参数
		Screen{
			Size:5.5,
			Res_X:1920,
			Res_Y:1080,
		},
		//电池参数
		Battery{
			2910,
		},
		//是否有指纹识别
		true,
	}
	//将数据序列化为JSON
	json_Data,_:=json.Marshal(raw)
	return json_Data
}

func main()  {
	//生成一段JSON数据
	json_Data:=gen_Json_Data()
	fmt.Println(string(json_Data))
	//只需要屏幕和指纹识别信息的结构和实例
	screen_And_Touch:= struct {
		Screen
		Has_Touch_ID bool
	}{}
	//反序列化到screen_And_Touch
	json.Unmarshal(json_Data,&screen_And_Touch)
	//输出screen_And_Touch的详细结构
	fmt.Printf("%+v\n",screen_And_Touch)
	//只需要电池和指纹识别信息的结构和实例
	battery_And_Touch:= struct {
		Battery
		Has_Touch_ID bool
	}{}
	//反序列化到battery_And_Touch
	json.Unmarshal(json_Data,battery_And_Touch)
	//输出battery_And_Touch的详细结构
	fmt.Printf("%+v\n",battery_And_Touch)
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值