struct
Go使用结构体和结构体成员来描述真实世界的实体和实例对应的各种属性。
Go中的类型可以被实例化,使用new或&构造的类型实例的类型是类型的指针。
结构体成员由一系列的成员变量构成,这些成员变量也被称为”字段”。
- 字段拥有自己的类型和值;
- 字段名必须唯一;
- 字段的类型也可以是结构体,甚至是字段所在结构体的类型
Go的结构体与”类”都是符合结构体,但Go中结构体的内嵌配合接口比面向对象具有更高的扩展性和灵活性。
Go不仅结构体能拥有方法,且每种自定义类型也可以拥有自己的方法。
定义结构体
type 类型名struct { 字段1 字段1类型 字段2 字段2类型 … }
实例化结构体————为结构体分配内存并初始化
结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正的分配内存。因此必须在定义结构体并实例化后才能使用结构体的字段。
实例化就是根据结构体定义的格式创建一份与格式一致的内存区域,结构体实例与实例间的内存是完全独立的。
基本的实例化形式
var ins T
# T 为结构体类型
#ins为结构体的实例
创建指针类型的结构体
Go可以使用new关键字对类型(包括结构体、整形、浮点型、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。
ins := new(T)
# ins: T类型被实例化后保存到ins变量中,ins的类型为*T,属于指针。
Go可以像访问普通结构体一样使用”.”访问结构体指针的成员。
type Player struct {
Name string
Health_Point int
Magic_Point int
}
tank := new(Player)
tank.Name = "Canon"
tank.Health_Point = 300
取结构体的地址实例化
ins := &T{}
# ins为结构体的实例,类型为*T,是指针类型
初始化结构体的成员变量
使用健值对初始化结构体
ins := 结构体类型名{
字段1:字段1的值
字段2:字段2的值
...
}
使用多个值的列表初始化结构体
ins := 结构体类型名{
字段1的值,
字段2的值,
}
初始化匿名结构体
匿名结构体没有类型名称,无须通过type关键字定义就可以直接使用。
ins := struct {
// 匿名结构体字段定义
字段1 字段类型1
字段2 字段类型2
...
}{
// 字段值初始化,可以不初始化
初始化字段1: 字段1的值,
初始化字段2:字段2的值,
...
}
实例
package main
import "fmt"
func main(){
// 实例化一个匿名结构体
msg := &struct{
id int
data string
}{
1024,
"hello",
}
printMsg(msg)
}
// 接入匿名结构体,打印消息
func printMsg(msg *struct{
id int
data string
}) {
fmt.Println("%T\n",msg)
}
匿名结构体在使用时需要重新定义,造成大量重复的代码,因此开发中较少使用。
构造函数————结构体和类型的一系列初始化操作的函数封装
Go的类型或结构体没有构造函数的功能。结构体的初始化过程可以使用函数封装实现。
接收器
在 Go 中,我们可以为一个函数指定其唯一的接收器,接收器可以为任意类型,具备接收器的函数在 Go 中被称作方法。
接收器可以分为指针类型和非指针类型(值类型):
- 在方法内部对指针类型的接收器修改将会直接反馈到原接收器;
- 而非指针类型的接收器在方法中被操作的数据为原接收器的值拷贝,对其修改并不会影响到原接收器的数据。
具体使用时可以根据需要指定接收器的类型,比如当接收器占用内存较大或者需要对原接收器的属性进行修改时,可以使用指针类型接收器;当接收器占用内存较小,且方法只会读取接收器内的属性时,可以采用非指针类型接收器。
type Foo struct {
val int
}
func (f Foo) Inc(inc int) {
f.val += inc
}
func main() {
var f Foo
f.Inc(100)
fmt.Println(f.val)
}
// 输出 0。使用值类型接收者定义的方法,调用的时候,使用的是值的副本,对副本操作不会影响的原来的值。如果想要在调用函数中修改原值,可以使用指针接收者定义的方法
值接受和指针接收器区别:
值接受者 | 指针接收者 | |
---|---|---|
值调用者 | 方法会使用调用者的一个副本,类似于“传值“ | 使用值的引用来调用方法,上例中,yuting.growUp() 实际上是 (&yuting).growUp() |
指针调用者 | 指针被解引用为值,上例中,yt.howOld() 实际上是 (*yt).howOld() | 实际上也是“传值”,方法里的操作会影响到调用者,类似于指针传参,拷贝了一份指针 |
参考:https://blog.csdn.net/u010853261/article/details/100941972
使用总结:
如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。
使用指针作为方法的接收者的理由:
- 方法能够修改接收者指向的值。
- 避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。
多种方式创建和初始化结构体————模拟构造函数重载
结构体描述猫的特性。
type Cat struct {
Color string
Name string
}
func NewCatByName(name string) *Cat {
return &Cat{
Name: name,
}
}
func NewcatByColor(color string) *Cat {
return &Cat{
Color: color,
}
}
带有父子关系结构体的构造和初始化————模拟父级构造调用
派生,继承
type Cat struct {
Color string
Name string
}
type BlackCat struct {
Cat // 嵌入Cat,类似派生
}
// 构造基类
fun NewCat(name string) *Cat {
return &Cat{
Name: name,
}
}
// 构造子类
func NewBlackCat(color string) *BlackCat {
cat := &BlcakCat{}
cat.Color = color
return cat
}
Go语言中没有提供构造函数相关的特殊机制,用户根据自己的需求,将参数使用函数传递到结构体构造参数中即可完成构造函数的任务。
类型嵌套和结构体嵌套
类型嵌套或匿名字段:结构体允许其成员字段再声明时没有字段名而只有类型
type Data struct {
int
float32
bool
}
ins := &Data{
int: 10,
float32: 3.14,
bool: true,
}
类型内嵌仍然拥有自己的字段名,只是字段名就是其类型本身。结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只有一个。
结构体实例化后,如果匿名的字段类型为结构体,可直接访问匿名结构体里的所有成员,这种方式被称为结构体内嵌。
声明结构体内嵌
type BasicColor struct {
R, G, B float32
}
type Color struct {
BasicColor
Alpha float32
}
func main() {
var c Color
c.R = 1
c.G = 1
c.B = 0
c.Alpha = 1
fmt.Printf("%+v",c)
}
将Basic Color结构体嵌入到Color结构体中,Basic Color没有字段名而只有类型,这种写法就叫做结构体内嵌
结构内嵌特性
- 内嵌的结构体可以直接访问其成员变量
- 内嵌结构体的字段名是它的类型名
面向对象——继承
// 可飞行
type Flying struct{}
func (f *Walkable) Fly() {
fmt.Println("can fly")
}
// 可行走
type Walkable struct{}
func (f *Walkable) Walk() {
fmt.Println("can walk")
}
// 人类
type Human struct {
Walkable
}
// 鸟类
type Bird struct {
Walkable // 可走
Flying // 可飞行
}
func main() {
b := new(Bird)
b.Fly()
b.Walk()
}
使用Go语言的内嵌结构体实现对象特性,可以自由地在对象中增、删、改各种特性。
初始化结构体内嵌
结构体内嵌初始化时,将结构体内嵌的类型作为字段名像普通结构体一样进行初始化
// 车轮
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: 1.43,
}
}
fmt.Println("%+v\n",c)
}
使用场景
使用结构体解析JSON数据
// 获取http内容
url := "https://xxxxxxxx"
resg, err := http.Get(url)
defer resg.Body.Close()
if err!=nil {
panic(fmt.Sprintf("err: %q",err))
}
if( resg.StatusCode!=200 ){
Checkerr(errors.New("请求失败"))
}
msg, err := ioutil.ReadAll(resg.Body)
if err!=nil {
panic(fmt.Sprintf("err: %q",err))
}
var m Message
err1 := json.Unmarshal(msg,&m)
if err1!=nil {
panic(fmt.Sprintf("err: %q",err))
}
type Message struct {
Data struct {
List []struct {
ImageID string `json:"image_id"`
Width int `json:"width"`
Height int `json:"height"`
FileSize int `json:"file_size"`
Type string `json:"type"`
Signature string `json:"signature"`
SourceType string `json:"source_type"`
SourceSignature string `json:"source_signature"`
PreviewURL string `json:"preview_url"`
CreatedTime int `json:"created_time"`
LastModifiedTime int `json:"last_modified_time"`
} `json:"list"`
PageInfo struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalNumber int `json:"total_number"`
TotalPage int `json:"total_page"`
} `json:"page_info"`
} `json:"data"`
Code int `json:"code"`
Message string `json:"message"`
}
解析接口数据:https://mholt.github.io/json-to-go/