结构体是类型中带有成员的复合类型。go语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性。
go语言中的类型可以被实例化,使用new和&构造类型实例的类型是类型的指针。
结构体成员是由一系列成员变量构成,成员(字段)有以下属性:
- 字段名称唯一;
- 拥有类型和值;
- 成员的类型可以是结构体,甚至是字段所在结构体的类型。
go不支持”类“的概念,也不支持"继承"面向对象的概念。
go 语言不仅认为结构体能拥有方法,且每种自定义类型也可以有自己的方法。
在函数外部定义结构体,作用域是全局的。
结构体基础
定义结构体
type 结构体名称 struct {
字段名 字段类型
...
}
- 结构体类型名在包内不能重复
示例,表示直角坐标系中的点的坐标
type Point struct {
X int
Y int
}
实例化结构体--为结构体分配内存并初始化
结构体定义只是一种内存布局的描述,只有结构化后才能真正分配内存。
基本的实例化形式
示例,表示直角坐标系中的点的坐标
type Point struct {
X int
Y int
}
var p Point
p.x = 10
p.y = 20
创建指针类型的结构体
使用new关键字对类型进行实例化,会形成指针类型的结构体。
type Player struct {
Name string
HealthPoint int
MagicPoint int
}
tank := new(Player)
tank.name = "Canon"
tank.HealthPoint = 300
go 语言中访问成员变量可以继续使用”.“,go增加了语法糖,将
ins.Name
转化为(*ins).Name
。
取结构体地址实例化
对结构体进行&
取地址,视为对该类型进行一次new
实例化操作。
ins := &T{}
- T表示结构体类型
- Ins 为结构体实例,类型是
*T
示例,定义一个命令行指令,指令包含名臣,关联变量和注释
type Command struct {
Name string
Var *int // 此处使用指针,可以随时与绑定的值保持同步
Comment string
}
var version int = 1
cmd := &Command{}
cmd.Name = "version"
cmd.Var = &version
cmd.Comment = "show version"
初始化结构体成员变量
使用基本形式初始化结构体
示例,颜色的RGB值
type Color struct {
R, G, B byte
}
使用键值对初始化结构体
示例,描述家庭任务关联
type People struct {
name string
child *People
}
relationship := &People{
name: "grand father",
child: &People{
name: "Dad",
child: &People{
name: "I",
},
},
}
fmt.Println(relationship.child.child.name)
使用多个值列表初始化结构体
注意
- 必须初始化结构体的所有字段
- 初始值与字段顺序保持一致
- 键值对与值列表的形式不能混用
示例,地址
type Address struct {
Privince string
City string
ZipCode int
PhoneNumber string
}
addr := Address{
"上海",
"上海",
"222300",
"0"
}
构造函数--结构体初始化的函数封装
go语言或者结构体没有构造函数的功能,结构体的初始化的过程可以用函数封装实现。
模拟构造函数重载
示例,根据颜色和名称可以构造不同猫的实例。
type Cat struct {
Name string
COlor string
}
func NewCatByName(name string) *Cat {
return &Cat{
Name: name,
}
}
func NewCatByColor(color string) *Cat {
return &Cat{
Color: color,
}
}
模拟父级构造调用
示例,黑猫是一种猫,猫是黑猫的泛称。
package main
import "fmt"
type Cat struct {
Name string
Color string
}
type BlackCat struct {
Cat // 嵌入Cat,类似于派生
}
func NewCat(name string) *Cat {
return &Cat{
Name: name,
}
}
func NewBlackCat(name string) *BlackCat {
cat := &BlackCat{}
cat.Color = "black"
cat.Name = name
return cat
}
func main() {
cat := NewCat("wow")
fmt.Println(cat)
blackcat := NewBlackCat("hello")
fmt.Println(blackcat)
}
方法
使用背包作为"对象",将物品放入背包的过程称为”方法“。
面向过程的实现方法
面向过程中没有“方法”概念,只能通过结构体和函数,由使用者用函数参数和调用关系来形成接近”方法“的概念。
package main
import "fmt"
type Bag struct {
items []int
}
func Insert(b *Bag, itemid int) {
b.items = append(b.items, itemid)
}
func main() {
bag := &Bag{}
Insert(bag, 100)
fmt.Println(bag.items)
}
Go 语言结构体方法
将背包及放入背包物品中使用go语言的结构体和方法方式编写,为 *Bag
创建一个方法。
package main
import "fmt"
type Bag struct {
items []int
}
func (b *Bag) Insert(itemid int) {
b.items = append(b.items, itemid)
}
func main() {
bag := &Bag{}
bag.Insert(100)
fmt.Println(bag.items)
}
Insert(itemid int)
的写法与函数一致,(b *Bag)
表示接收器,即Insert
作用的对象实例。每个方法只能有一个接收器。
接收器--方法作用的目标
func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {}
指针和非指针接收器的使用
在计算机中,小对象由于值复制时的速度较快,适合使用非针接收器。大对象因为复制性能较低,适合使用指针接收器,在接收器和参数间传递 不进行复制, 只是传指针。
指针类型接收器
指针类型接收器由一个结构体指针组成,更接近于面向对象中的this
和self
。
由于指针的特性,调用方法时,修改接收器成员变量,在方法结束后是有效的。
package main
import "fmt"
type Property struct {
value int
}
func (p *Property) SetValue(value int) {
p.value = value
}
func (p *Property) Value() int {
return p.value
}
func main() {
p := &Property{}
p.SetValue(1)
fmt.Println(p.Value())
}
非指针类型接收器
当方法作用于非指针接收器时,go语言会在代码运行时将接收器的值复制一份。在非指针接收器的方法中可以获取接收器的成员值,但修改后无效。
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{3, 4}
fmt.Println(p1.Add(p2))
}
示例,time时间的中的Second属性是 Duration
,Duration拥有String()方法。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println(time.Second.String())
}
//const (
// Nanosecond Duration = 1
// Microsecond = 1000 * Nanosecond
// Millisecond = 1000 * Microsecond
// Second = 1000 * Millisecond
// Minute = 60 * Second
// Hour = 60 * Minute
//)
//
//func (d Duration) String() string {
// ...
// return string(buf[w:])
结构体内嵌
结构体允许其成员字段在声明时没有宇段名而只有类型,这种形式的字段被称为类型内嵌。
type Data struct {
int
float32
bool
}
ins : = &Data{
int: 1 0,
float32 : 3 .14,
bool: true
}
结构体类型内嵌比普通类型内嵌的概念复杂 ,下面通过一个实例来理解。
package main
import "fmt"
type BasicColor struct {
R, G, B float32
}
type Color struct {
Basic BasicColor
Alpha float32
}
func main() {
var color Color
color.Basic.R = 1
color.Basic.G = 1
color.Basic.B = 0
color.Alpha = 1
fmt.Printf("%+v", color)
}
将 BasicColor 结构体嵌入Color 结构体中, BasicColor 没有宇段名而只有类型,这种写法就叫做结构体内嵌。
package main
import "fmt"
type BasicColor struct {
R, G, B float32
}
type Color struct {
BasicColor
Alpha float32
}
func main() {
var color Color
color.R = 1
color.G = 1
color.B = 0
color.Alpha = 1
fmt.Printf("%+v", color)
}
初始化结构体内嵌
package main
import "fmt"
type Wheel struct {
Size int
}
type Engine struct {
Power int
Type string
}
type Car struct {
Wheel
Engine
}
func main() {
car := Car{
Wheel: Wheel{
Size: 10,
},
Engine: Engine{
Power: 10,
Type: "Dz",
},
}
fmt.Printf("%+v", car)
}
使用匿名结构体分离json数据
package main
import (
"encoding/json"
"fmt"
)
// 定义屏幕
type Screen struct {
Size float32 // 屏幕尺寸
ResX, ResY int //屏幕水平和垂直分辨率
}
// 定义电池
type Battery struct {
Capacity int // 容量
}
func genJsonData () []byte {
raw := &struct {
Screen
Battery
HasTouchID bool
}{
Screen: Screen{
Size: 5.5,
ResX: 1920,
ResY: 1080,
},
Battery: Battery{
Capacity: 100,
},
HasTouchID: true,
}
jsonData, err := json.Marshal(raw)
if err != nil {
return make([]byte, 0)
} else {
return jsonData
}
}
func main() {
// 生成一段 JSON 数据
jsonData := genJsonData()
fmt.Println(string(jsonData))
// 只需要屏幕和指纹识别信息的结构和实例
screenAndTouch := struct {
Screen
HasTouchID bool
}{}
// 反序列化到 screenAndTouch
err := json.Unmarshal(jsonData, &screenAndTouch)
if err == nil {
fmt.Printf("%+v
", screenAndTouch)
}
// 只需要电池和指纹识别信息的结构和实例
batteryAndTouch := struct {
Battery
HasTouchID bool
}{}
err = json.Unmarshal(jsonData, &batteryAndTouch)
if err == nil {
fmt.Printf("%+v
", &batteryAndTouch)
}
}